2016-01-05 02:20:46 +00:00
|
|
|
/**
|
|
|
|
* Copyright (c) 2015, The CyanogenMod 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 org.cyanogenmod.tests.settings.unit;
|
|
|
|
|
|
|
|
import android.app.INotificationManager;
|
|
|
|
import android.content.Context;
|
2016-06-15 01:07:55 +00:00
|
|
|
import android.media.AudioAttributes;
|
|
|
|
import android.media.AudioFormat;
|
|
|
|
import android.media.AudioManager;
|
|
|
|
import android.media.AudioRecord;
|
|
|
|
import android.media.MediaRecorder;
|
2016-01-05 02:20:46 +00:00
|
|
|
import android.net.Uri;
|
|
|
|
import android.os.RemoteException;
|
|
|
|
import android.os.ServiceManager;
|
|
|
|
import android.service.notification.Condition;
|
|
|
|
import android.service.notification.IConditionListener;
|
|
|
|
import android.test.AndroidTestCase;
|
2016-02-04 00:49:18 +00:00
|
|
|
import android.test.FlakyTest;
|
2016-01-05 02:20:46 +00:00
|
|
|
import android.test.suitebuilder.annotation.SmallTest;
|
|
|
|
import android.provider.Settings;
|
|
|
|
import android.text.format.DateUtils;
|
|
|
|
import android.util.Log;
|
2016-03-25 19:43:22 +00:00
|
|
|
|
|
|
|
import cyanogenmod.app.CMContextConstants;
|
2016-01-05 02:20:46 +00:00
|
|
|
import cyanogenmod.app.PartnerInterface;
|
|
|
|
|
|
|
|
import java.util.List;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Unit test for PartnerInterface
|
|
|
|
*/
|
|
|
|
public class CMPartnerInterfaceTest extends AndroidTestCase {
|
|
|
|
|
|
|
|
private static final String TAG = "CMPartnerInterfaceTest";
|
|
|
|
|
|
|
|
private PartnerInterface mPartnerInterface;
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void setUp() throws Exception {
|
|
|
|
super.setUp();
|
2016-03-25 19:43:22 +00:00
|
|
|
// Only run this if we support partner interfaces
|
|
|
|
org.junit.Assume.assumeTrue(mContext.getPackageManager().hasSystemFeature(
|
|
|
|
CMContextConstants.Features.PARTNER));
|
2016-01-05 02:20:46 +00:00
|
|
|
mPartnerInterface = PartnerInterface.getInstance(getContext());
|
|
|
|
|
|
|
|
setupAirplaneModeTests();
|
|
|
|
setupZenModeTests();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void tearDown() throws Exception {
|
|
|
|
super.tearDown();
|
|
|
|
teardownAirplaneModeTests();
|
|
|
|
teardownZenModeTests();
|
|
|
|
}
|
|
|
|
|
|
|
|
@SmallTest
|
|
|
|
public void testPartnerInterfaceExists() {
|
|
|
|
assertNotNull(mPartnerInterface);
|
|
|
|
}
|
|
|
|
|
|
|
|
@SmallTest
|
|
|
|
public void testPartnerInterfaceAvailable() {
|
|
|
|
assertNotNull(mPartnerInterface.getService());
|
|
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////
|
|
|
|
// Airplane Mode tests
|
|
|
|
|
|
|
|
private boolean mAirplaneModeEnabled;
|
|
|
|
private void setupAirplaneModeTests() {
|
|
|
|
// Remember the initial state
|
|
|
|
mAirplaneModeEnabled = getAirplaneModeEnabled();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void teardownAirplaneModeTests() {
|
|
|
|
// Restore airplane mode
|
|
|
|
mPartnerInterface.setAirplaneModeEnabled(mAirplaneModeEnabled);
|
|
|
|
}
|
|
|
|
@SmallTest
|
|
|
|
public void testSetAirplaneModeOn() {
|
|
|
|
mPartnerInterface.setAirplaneModeEnabled(true);
|
|
|
|
assertTrue(getAirplaneModeEnabled());
|
|
|
|
}
|
|
|
|
|
|
|
|
@SmallTest
|
|
|
|
public void testSetAirplaneModeOff() {
|
|
|
|
mPartnerInterface.setAirplaneModeEnabled(false);
|
|
|
|
assertTrue(getAirplaneModeEnabled() == false);
|
|
|
|
}
|
|
|
|
|
|
|
|
private boolean getAirplaneModeEnabled() {
|
|
|
|
return Settings.System.getInt(getContext().getContentResolver(),
|
|
|
|
Settings.System.AIRPLANE_MODE_ON, 0) == 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////
|
|
|
|
// Zen Mode Testing:
|
|
|
|
// Allow the tests below for now, but in the future
|
|
|
|
// implement a valid method for restoring the zen mode
|
|
|
|
// setting, e.g. in tearDownZenModeTests
|
|
|
|
|
|
|
|
// Because it's not possible to get & restore zen mode
|
|
|
|
// duration, these tests will contaminate system state
|
|
|
|
// whenver they're run. Ideally testing should instead
|
|
|
|
// be done in another suite, such as using Mokito
|
|
|
|
// https://corner.squareup.com/2012/10/mockito-android.html
|
|
|
|
//
|
|
|
|
// However because of the complexity, for now allow
|
|
|
|
// these unit tests as-is.
|
|
|
|
//
|
|
|
|
// THESE WILL WIPE OUT any duration-based zen modes in
|
|
|
|
// effect!
|
|
|
|
private static final long DURATION_30s_MS = 30 * DateUtils.SECOND_IN_MILLIS;
|
|
|
|
private static final long DURATION_TOLERANCE_MS = 5; //5 ms in tolerance due to latency
|
|
|
|
private static final long DURATION_TEST_MAX_MS = DateUtils.MINUTE_IN_MILLIS; //Allow 1 minute max for unit testing
|
|
|
|
|
|
|
|
private INotificationManager mNotificationManager;
|
|
|
|
private CountdownConditionListener mConditionListener;
|
|
|
|
|
|
|
|
private void setupZenModeTests() {
|
|
|
|
mNotificationManager = INotificationManager.Stub.asInterface(
|
|
|
|
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
|
|
|
|
|
|
|
|
mConditionListener = new CountdownConditionListener();
|
|
|
|
try {
|
|
|
|
mNotificationManager.requestZenModeConditions(mConditionListener, Condition.FLAG_RELEVANT_ALWAYS);
|
|
|
|
} catch (RemoteException e) {
|
|
|
|
fail("requestZenModeConditions exception " + e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void teardownZenModeTests() {
|
|
|
|
// For now, restore the zen mode to the system default
|
|
|
|
mPartnerInterface.setZenMode(PartnerInterface.ZEN_MODE_OFF);
|
|
|
|
}
|
|
|
|
|
|
|
|
@SmallTest
|
|
|
|
public void testSetZenModeImportantInterruptions() {
|
|
|
|
mPartnerInterface.setZenMode(PartnerInterface.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
|
|
|
|
assertEquals(PartnerInterface.ZEN_MODE_IMPORTANT_INTERRUPTIONS, getZenMode());
|
|
|
|
}
|
|
|
|
|
2016-06-16 21:02:58 +00:00
|
|
|
@FlakyTest(tolerance = 5)
|
2016-01-05 02:20:46 +00:00
|
|
|
@SmallTest
|
|
|
|
public void testSetZenModeImportantInterruptionsWithDurations() {
|
|
|
|
// 0 duration
|
|
|
|
testZenModeWithDuration(PartnerInterface.ZEN_MODE_IMPORTANT_INTERRUPTIONS, 0);
|
|
|
|
|
|
|
|
// Indefinitely
|
|
|
|
testZenModeWithDuration(PartnerInterface.ZEN_MODE_IMPORTANT_INTERRUPTIONS, Long.MAX_VALUE);
|
|
|
|
testZenModeWithDuration(PartnerInterface.ZEN_MODE_IMPORTANT_INTERRUPTIONS, -1);
|
|
|
|
|
|
|
|
// normal duration values (1, 5s)
|
|
|
|
// NOTE: these tests do not return until duration has passed. Use with care!
|
|
|
|
testZenModeWithDuration(PartnerInterface.ZEN_MODE_IMPORTANT_INTERRUPTIONS, DateUtils.SECOND_IN_MILLIS);
|
|
|
|
testZenModeWithDuration(PartnerInterface.ZEN_MODE_IMPORTANT_INTERRUPTIONS, 5 * DateUtils.SECOND_IN_MILLIS);
|
|
|
|
}
|
|
|
|
|
|
|
|
@SmallTest
|
|
|
|
public void testSetZenModeNoInterruptions() {
|
|
|
|
mPartnerInterface.setZenMode(PartnerInterface.ZEN_MODE_NO_INTERRUPTIONS);
|
|
|
|
assertEquals(PartnerInterface.ZEN_MODE_NO_INTERRUPTIONS, getZenMode());
|
|
|
|
}
|
|
|
|
|
2016-06-16 21:02:58 +00:00
|
|
|
@FlakyTest(tolerance = 5)
|
2016-01-05 02:20:46 +00:00
|
|
|
@SmallTest
|
|
|
|
public void testSetZenModeNoInterruptionsWithDurations() {
|
|
|
|
// 0 duration
|
|
|
|
testZenModeWithDuration(PartnerInterface.ZEN_MODE_NO_INTERRUPTIONS, 0);
|
|
|
|
|
|
|
|
// Indefinitely
|
|
|
|
testZenModeWithDuration(PartnerInterface.ZEN_MODE_NO_INTERRUPTIONS, Long.MAX_VALUE);
|
|
|
|
testZenModeWithDuration(PartnerInterface.ZEN_MODE_NO_INTERRUPTIONS, -1);
|
|
|
|
|
|
|
|
// normal duration values (1, 5s)
|
|
|
|
// NOTE: these tests do not return until duration has passed. Use with care!
|
|
|
|
testZenModeWithDuration(PartnerInterface.ZEN_MODE_NO_INTERRUPTIONS, DateUtils.SECOND_IN_MILLIS);
|
|
|
|
testZenModeWithDuration(PartnerInterface.ZEN_MODE_NO_INTERRUPTIONS, 5 * DateUtils.SECOND_IN_MILLIS);
|
|
|
|
}
|
|
|
|
|
|
|
|
@SmallTest
|
|
|
|
public void testSetZenModeOff() {
|
|
|
|
mPartnerInterface.setZenMode(PartnerInterface.ZEN_MODE_OFF);
|
|
|
|
assertEquals(PartnerInterface.ZEN_MODE_OFF, getZenMode());
|
|
|
|
}
|
|
|
|
|
2016-06-15 01:07:55 +00:00
|
|
|
private final static int BUFFER_ELEMENTS_TO_REC = 1024;
|
|
|
|
private final static int BYTES_PER_ELEMENT = 2;
|
|
|
|
private static final int RECORDER_SAMPLERATE = 41000;
|
|
|
|
private static final int RECORDER_CHANNELS = AudioFormat.CHANNEL_IN_MONO;
|
|
|
|
private static final int RECORDER_AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
|
|
|
|
|
|
|
|
private static int[] mSampleRates = new int[] { 8000, 11025, 22050, 44100 };
|
|
|
|
public AudioRecord findAudioRecord() {
|
|
|
|
for (int rate : mSampleRates) {
|
|
|
|
for (short audioFormat : new short[] { AudioFormat.ENCODING_PCM_8BIT,
|
|
|
|
AudioFormat.ENCODING_PCM_16BIT }) {
|
|
|
|
for (short channelConfig : new short[] { AudioFormat.CHANNEL_IN_MONO,
|
|
|
|
AudioFormat.CHANNEL_IN_STEREO }) {
|
|
|
|
try {
|
|
|
|
Log.d(TAG, "Attempting rate " + rate + "Hz, bits: " + audioFormat + ", channel: "
|
|
|
|
+ channelConfig);
|
|
|
|
int bufferSize = AudioRecord.getMinBufferSize(rate, channelConfig, audioFormat);
|
|
|
|
|
|
|
|
if (bufferSize != AudioRecord.ERROR_BAD_VALUE) {
|
|
|
|
AudioRecord recorder = new AudioRecord(
|
|
|
|
cyanogenmod.media.MediaRecorder.AudioSource.HOTWORD,
|
|
|
|
rate, channelConfig, audioFormat, bufferSize);
|
|
|
|
|
|
|
|
if (recorder.getState() == AudioRecord.STATE_INITIALIZED)
|
|
|
|
return recorder;
|
|
|
|
}
|
|
|
|
} catch (Exception e) {
|
|
|
|
Log.e(TAG, rate + "Exception, keep trying.",e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
@SmallTest
|
|
|
|
public void testGetCurrentHotwordPackageName() {
|
|
|
|
// make sure no one is actively stealing this as we attempt to
|
|
|
|
assertNull(mPartnerInterface.getCurrentHotwordPackageName());
|
|
|
|
|
|
|
|
// find first viable audio record
|
|
|
|
final AudioRecord audioRecorder = findAudioRecord();
|
|
|
|
|
|
|
|
audioRecorder.startRecording();
|
|
|
|
assertEquals(mContext.getPackageName(), mPartnerInterface.getCurrentHotwordPackageName());
|
|
|
|
audioRecorder.stop();
|
|
|
|
}
|
|
|
|
|
2016-01-05 02:20:46 +00:00
|
|
|
/**
|
|
|
|
* testZenModeWithDuration: sets Zen Mode with duration and blocks
|
|
|
|
* until the duration is verified. Use with care! This does not
|
|
|
|
* return until after durationMillis
|
|
|
|
* @param mode
|
|
|
|
* @param durationMillis
|
|
|
|
*/
|
|
|
|
private void testZenModeWithDuration(int mode, long durationMillis) {
|
|
|
|
|
|
|
|
final long startTimeMillis = System.currentTimeMillis();
|
|
|
|
mPartnerInterface.setZenModeWithDuration(mode, durationMillis);
|
|
|
|
final int actualZenMode = getZenMode();
|
|
|
|
|
|
|
|
// check zen mode is correct
|
|
|
|
if (durationMillis == 0) {
|
|
|
|
assertTrue(actualZenMode == mode || actualZenMode == PartnerInterface.ZEN_MODE_OFF);
|
|
|
|
} else {
|
|
|
|
assertEquals(mode, actualZenMode);
|
|
|
|
}
|
|
|
|
// skip durations that are indefinite
|
|
|
|
if (actualZenMode == PartnerInterface.ZEN_MODE_OFF || durationMillis < 0 || durationMillis == Long.MAX_VALUE) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// check duration is correct
|
|
|
|
final long zenDuration = getZenModeDuration(startTimeMillis);
|
|
|
|
assertTrue(Math.abs(zenDuration - durationMillis) <= DURATION_TOLERANCE_MS); //Allow a tolerance
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* getZenModeDuration: Blocking call to wait for ZenMode duration.
|
|
|
|
* @param startTimeMillis - start time of the duration
|
|
|
|
* @return
|
|
|
|
*/
|
|
|
|
private long getZenModeDuration(long startTimeMillis) {
|
|
|
|
// NOTE: waits for the next duration to be triggered
|
|
|
|
return mConditionListener.waitForDuration(startTimeMillis);
|
|
|
|
}
|
|
|
|
|
|
|
|
private int getZenMode() {
|
|
|
|
int zenMode = -1;
|
|
|
|
try {
|
|
|
|
zenMode = mNotificationManager.getZenMode();
|
|
|
|
} catch (RemoteException rex) {
|
|
|
|
fail("INotificationManager.getZenMode() " + rex.getMessage());
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
Log.d(TAG, "getZenMode returning " + zenMode);
|
|
|
|
return zenMode;
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* CountdownConditionListener
|
|
|
|
* This class is passed to blocks until the Countdown is received
|
|
|
|
*/
|
|
|
|
private static class CountdownConditionListener
|
|
|
|
extends IConditionListener.Stub {
|
|
|
|
|
|
|
|
private static long INVALID_ENDTIME = -1;
|
|
|
|
private long mEndTime = INVALID_ENDTIME;
|
|
|
|
|
|
|
|
public CountdownConditionListener() {
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* waitForDuration: blocks until onConditionReceived
|
|
|
|
* This CountdownConditionListener was previously passed
|
|
|
|
* to the
|
|
|
|
* @return
|
|
|
|
* the duration of
|
|
|
|
*/
|
|
|
|
public synchronized long waitForDuration(long startTimeMillis) {
|
|
|
|
Log.d(TAG, "waitForDuration");
|
|
|
|
// If we have a stale endtime, then discard it
|
|
|
|
if (mEndTime < startTimeMillis) {
|
|
|
|
mEndTime = INVALID_ENDTIME;
|
|
|
|
}
|
|
|
|
// If no valid endtime, then block and wait for the current
|
|
|
|
// duration to expire. The wait ends when
|
|
|
|
// onConditionsReceived is called
|
|
|
|
if (mEndTime == INVALID_ENDTIME) {
|
|
|
|
try {
|
|
|
|
// wait no more than DURATION_TEST_MAX_MS
|
|
|
|
wait(DURATION_TEST_MAX_MS);
|
|
|
|
} catch (InterruptedException iex) {
|
|
|
|
Log.e(TAG, "waitForDuration", iex);
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (mEndTime == INVALID_ENDTIME) {
|
|
|
|
Log.d(TAG, "waitForDuration found invalid endtime. Did you exceed the max duration (" + DURATION_TEST_MAX_MS + " ms)?");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
Log.d(TAG, "waitForDuration returning endtime:" + mEndTime + " duration:" + (mEndTime - startTimeMillis));
|
|
|
|
final long duration = mEndTime - startTimeMillis;
|
|
|
|
|
|
|
|
// Reset endtime to show that it's been consumed
|
|
|
|
mEndTime = INVALID_ENDTIME;
|
|
|
|
return duration;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* onConditionReceived: called when a condition is triggered
|
|
|
|
* @param conditions - conditions that triggered
|
|
|
|
* This is actually just the Alarm endtime that CountdownConditionProvider
|
|
|
|
* previously submitted
|
|
|
|
* @throws RemoteException
|
|
|
|
*/
|
|
|
|
@Override
|
|
|
|
public synchronized void onConditionsReceived(Condition[] conditions) throws RemoteException {
|
|
|
|
// CountdownConditionProvider only triggers 1 condition at a time
|
|
|
|
mEndTime = parseEndTime(conditions[0].id);
|
|
|
|
notify();
|
|
|
|
}
|
|
|
|
|
|
|
|
private long parseEndTime(Uri conditionUri) {
|
|
|
|
final List<String> pathSegments = conditionUri.getPathSegments();
|
|
|
|
return Long.decode(pathSegments.get(pathSegments.size() - 1));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Private method for debugging
|
|
|
|
private void logConditions(Condition[] conditions) {
|
|
|
|
for (int i = 0; i < conditions.length; i++) {
|
|
|
|
Log.d(TAG, "condition[" + i + "]:" + conditions[i].id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|