diff options
Diffstat (limited to 'media')
55 files changed, 6950 insertions, 619 deletions
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index b6e4659..035b282 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -49,6 +49,7 @@ public class AudioManager { private final Context mContext; private long mVolumeKeyUpTime; private final boolean mUseMasterVolume; + private final boolean mUseVolumeKeySounds; private static String TAG = "AudioManager"; /** @@ -313,6 +314,13 @@ public class AudioManager { public static final int FLAG_VIBRATE = 1 << 4; /** + * Indicates to VolumePanel that the volume slider should be disabled as user + * cannot change the stream volume + * @hide + */ + public static final int FLAG_FIXED_VOLUME = 1 << 5; + + /** * Ringer mode that will be silent and will not vibrate. (This overrides the * vibrate setting.) * @@ -412,6 +420,8 @@ public class AudioManager { mContext = context; mUseMasterVolume = mContext.getResources().getBoolean( com.android.internal.R.bool.config_useMasterVolume); + mUseVolumeKeySounds = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_useVolumeKeySounds); } private static IAudioService getService() @@ -463,6 +473,7 @@ public class AudioManager { * responsive to the user. */ int flags = FLAG_SHOW_UI | FLAG_VIBRATE; + if (mUseMasterVolume) { adjustMasterVolume( keyCode == KeyEvent.KEYCODE_VOLUME_UP @@ -502,18 +513,17 @@ public class AudioManager { * Play a sound. This is done on key up since we don't want the * sound to play when a user holds down volume down to mute. */ - if (mUseMasterVolume) { - if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { + if (mUseVolumeKeySounds) { + if (mUseMasterVolume) { adjustMasterVolume(ADJUST_SAME, FLAG_PLAY_SOUND); + } else { + int flags = FLAG_PLAY_SOUND; + adjustSuggestedStreamVolume( + ADJUST_SAME, + stream, + flags); } - } else { - int flags = FLAG_PLAY_SOUND; - adjustSuggestedStreamVolume( - ADJUST_SAME, - stream, - flags); } - mVolumeKeyUpTime = SystemClock.uptimeMillis(); break; } @@ -1161,7 +1171,7 @@ public class AudioManager { /** * Indicates if current platform supports use of SCO for off call use cases. * Application wanted to use bluetooth SCO audio when the phone is not in call - * must first call thsi method to make sure that the platform supports this + * must first call this method to make sure that the platform supports this * feature. * @return true if bluetooth SCO can be used for audio when not in call * false otherwise @@ -1297,6 +1307,19 @@ public class AudioManager { } /** + * @hide + * Signals whether remote submix audio rerouting is enabled. + */ + public void setRemoteSubmixOn(boolean on, int address) { + IAudioService service = getService(); + try { + service.setRemoteSubmixOn(on, address); + } catch (RemoteException e) { + Log.e(TAG, "Dead object in setRemoteSubmixOn", e); + } + } + + /** * Sets audio routing to the wired headset on or off. * * @param on set <var>true</var> to route audio to/from wired @@ -1503,6 +1526,16 @@ public class AudioManager { /** * @hide + * Checks whether speech recognition is active + * @return true if a recording with source {@link MediaRecorder.AudioSource#VOICE_RECOGNITION} + * is underway. + */ + public boolean isSpeechRecognitionActive() { + return AudioSystem.isSourceActive(MediaRecorder.AudioSource.VOICE_RECOGNITION); + } + + /** + * @hide * If the stream is active locally or remotely, adjust its volume according to the enforced * priority rules. * Note: only AudioManager.STREAM_MUSIC is supported at the moment @@ -2427,4 +2460,40 @@ public class AudioManager { return null; } } + + /** + * Used as a key for {@link #getProperty} to request the native or optimal output sample rate + * for this device's primary output stream, in decimal Hz. + */ + public static final String PROPERTY_OUTPUT_SAMPLE_RATE = + "android.media.property.OUTPUT_SAMPLE_RATE"; + + /** + * Used as a key for {@link #getProperty} to request the native or optimal output buffer size + * for this device's primary output stream, in decimal PCM frames. + */ + public static final String PROPERTY_OUTPUT_FRAMES_PER_BUFFER = + "android.media.property.OUTPUT_FRAMES_PER_BUFFER"; + + /** + * Returns the value of the property with the specified key. + * @param key One of the strings corresponding to a property key: either + * {@link #PROPERTY_OUTPUT_SAMPLE_RATE} or + * {@link #PROPERTY_OUTPUT_FRAMES_PER_BUFFER} + * @return A string representing the associated value for that property key, + * or null if there is no value for that key. + */ + public String getProperty(String key) { + if (PROPERTY_OUTPUT_SAMPLE_RATE.equals(key)) { + int outputSampleRate = AudioSystem.getPrimaryOutputSamplingRate(); + return outputSampleRate > 0 ? Integer.toString(outputSampleRate) : null; + } else if (PROPERTY_OUTPUT_FRAMES_PER_BUFFER.equals(key)) { + int outputFramesPerBuffer = AudioSystem.getPrimaryOutputFrameCount(); + return outputFramesPerBuffer > 0 ? Integer.toString(outputFramesPerBuffer) : null; + } else { + // null or unknown key + return null; + } + } + } diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index a993381..f0fdfc1 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -59,11 +59,13 @@ import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; +import android.os.UserHandle; import android.os.Vibrator; import android.provider.Settings; import android.provider.Settings.System; import android.speech.RecognizerIntent; import android.telephony.PhoneStateListener; +import android.telephony.ServiceState; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; @@ -149,12 +151,18 @@ public class AudioService extends IAudioService.Stub implements OnFinished { private static final int MSG_REEVALUATE_REMOTE = 17; private static final int MSG_RCC_NEW_PLAYBACK_INFO = 18; private static final int MSG_RCC_NEW_VOLUME_OBS = 19; + private static final int MSG_SET_FORCE_BT_A2DP_USE = 20; // start of messages handled under wakelock // these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(), // and not with sendMsg(..., ..., SENDMSG_QUEUE, ...) - private static final int MSG_SET_WIRED_DEVICE_CONNECTION_STATE = 20; - private static final int MSG_SET_A2DP_CONNECTION_STATE = 21; + private static final int MSG_SET_WIRED_DEVICE_CONNECTION_STATE = 21; + private static final int MSG_SET_A2DP_CONNECTION_STATE = 22; // end of messages handled under wakelock + private static final int MSG_SET_RSX_CONNECTION_STATE = 23; // change remote submix connection + private static final int MSG_CHECK_MUSIC_ACTIVE = 24; + private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 25; + private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME = 26; + private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED = 27; // flags for MSG_PERSIST_VOLUME indicating if current and/or last audible volume should be // persisted @@ -387,10 +395,12 @@ public class AudioService extends IAudioService.Stub implements OnFinished { // message looper for SoundPool listener private Looper mSoundPoolLooper = null; // volume applied to sound played with playSoundEffect() - private static int SOUND_EFFECT_VOLUME_DB; - // getActiveStreamType() will return STREAM_NOTIFICATION during this period after a notification + private static int sSoundEffectVolumeDb; + // getActiveStreamType() will return: + // - STREAM_NOTIFICATION on tablets during this period after a notification stopped + // - STREAM_MUSIC on phones during this period after music or talkback/voice search prompt // stopped - private static final int NOTIFICATION_VOLUME_DELAY_MS = 5000; + private static final int DEFAULT_STREAM_TYPE_OVERRIDE_DELAY_MS = 5000; // previous volume adjustment direction received by checkForRingerModeChange() private int mPrevVolDirection = AudioManager.ADJUST_SAME; // Keyguard manager proxy @@ -422,6 +432,16 @@ public class AudioService extends IAudioService.Stub implements OnFinished { */ public final static int STREAM_REMOTE_MUSIC = -200; + // Devices for which the volume is fixed and VolumePanel slider should be disabled + final int mFixedVolumeDevices = AudioSystem.DEVICE_OUT_AUX_DIGITAL | + AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET | + AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET | + AudioSystem.DEVICE_OUT_ALL_USB; + + private final boolean mMonitorOrientation; + + private boolean mDockAudioMediaEnabled = true; + /////////////////////////////////////////////////////////////////////////// // Construction /////////////////////////////////////////////////////////////////////////// @@ -444,13 +464,27 @@ public class AudioService extends IAudioService.Stub implements OnFinished { "ro.config.vc_call_vol_steps", MAX_STREAM_VOLUME[AudioSystem.STREAM_VOICE_CALL]); - SOUND_EFFECT_VOLUME_DB = context.getResources().getInteger( + sSoundEffectVolumeDb = context.getResources().getInteger( com.android.internal.R.integer.config_soundEffectVolumeDb); mVolumePanel = new VolumePanel(context, this); mMode = AudioSystem.MODE_NORMAL; mForcedUseForComm = AudioSystem.FORCE_NONE; + createAudioSystemThread(); + + boolean cameraSoundForced = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_camera_sound_forced); + mCameraSoundForced = new Boolean(cameraSoundForced); + sendMsg(mAudioHandler, + MSG_SET_FORCE_USE, + SENDMSG_QUEUE, + AudioSystem.FOR_SYSTEM, + cameraSoundForced ? + AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE, + null, + 0); + readPersistedSettings(); mSettingsObserver = new SettingsObserver(); updateStreamVolumeAlias(false /*updateVolumes*/); @@ -458,6 +492,8 @@ public class AudioService extends IAudioService.Stub implements OnFinished { mMediaServerOk = true; + mSafeMediaVolumeState = new Integer(SAFE_MEDIA_VOLUME_NOT_CONFIGURED); + // Call setRingerModeInt() to apply correct mute // state on streams affected by ringer mode. mRingerModeMutedStreams = 0; @@ -475,12 +511,14 @@ public class AudioService extends IAudioService.Stub implements OnFinished { intentFilter.addAction(Intent.ACTION_BOOT_COMPLETED); intentFilter.addAction(Intent.ACTION_SCREEN_ON); intentFilter.addAction(Intent.ACTION_SCREEN_OFF); + intentFilter.addAction(Intent.ACTION_USER_SWITCHED); + intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); // Register a configuration change listener only if requested by system properties // to monitor orientation changes (off by default) - if (SystemProperties.getBoolean("ro.audio.monitorOrientation", false)) { + mMonitorOrientation = SystemProperties.getBoolean("ro.audio.monitorOrientation", false); + if (mMonitorOrientation) { Log.v(TAG, "monitoring device orientation"); - intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); // initialize orientation in AudioSystem setOrientationForAudioSystem(); } @@ -569,6 +607,8 @@ public class AudioService extends IAudioService.Stub implements OnFinished { mStreamStates[i].dump(pw); pw.println(""); } + pw.print("\n- mute affected streams = 0x"); + pw.println(Integer.toHexString(mMuteAffectedStreams)); } @@ -599,11 +639,33 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } } + private void readDockAudioSettings(ContentResolver cr) + { + mDockAudioMediaEnabled = Settings.Global.getInt( + cr, Settings.Global.DOCK_AUDIO_MEDIA_ENABLED, 0) == 1; + + if (mDockAudioMediaEnabled) { + mBecomingNoisyIntentDevices |= AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET; + } else { + mBecomingNoisyIntentDevices &= ~AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET; + } + + sendMsg(mAudioHandler, + MSG_SET_FORCE_USE, + SENDMSG_QUEUE, + AudioSystem.FOR_DOCK, + mDockAudioMediaEnabled ? + AudioSystem.FORCE_ANALOG_DOCK : AudioSystem.FORCE_NONE, + null, + 0); + } + private void readPersistedSettings() { final ContentResolver cr = mContentResolver; int ringerModeFromSettings = - System.getInt(cr, System.MODE_RINGER, AudioManager.RINGER_MODE_NORMAL); + Settings.Global.getInt( + cr, Settings.Global.MODE_RINGER, AudioManager.RINGER_MODE_NORMAL); int ringerMode = ringerModeFromSettings; // sanity check in case the settings are restored from a device with incompatible // ringer modes @@ -614,42 +676,66 @@ public class AudioService extends IAudioService.Stub implements OnFinished { ringerMode = AudioManager.RINGER_MODE_SILENT; } if (ringerMode != ringerModeFromSettings) { - System.putInt(cr, System.MODE_RINGER, ringerMode); + Settings.Global.putInt(cr, Settings.Global.MODE_RINGER, ringerMode); } synchronized(mSettingsLock) { mRingerMode = ringerMode; - } - // System.VIBRATE_ON is not used any more but defaults for mVibrateSetting - // are still needed while setVibrateSetting() and getVibrateSetting() are being deprecated. - mVibrateSetting = getValueForVibrateSetting(0, - AudioManager.VIBRATE_TYPE_NOTIFICATION, - mHasVibrator ? AudioManager.VIBRATE_SETTING_ONLY_SILENT - : AudioManager.VIBRATE_SETTING_OFF); - mVibrateSetting = getValueForVibrateSetting(mVibrateSetting, - AudioManager.VIBRATE_TYPE_RINGER, - mHasVibrator ? AudioManager.VIBRATE_SETTING_ONLY_SILENT - : AudioManager.VIBRATE_SETTING_OFF); - - // make sure settings for ringer mode are consistent with device type: non voice capable - // devices (tablets) include media stream in silent mode whereas phones don't. - mRingerModeAffectedStreams = Settings.System.getInt(cr, - Settings.System.MODE_RINGER_STREAMS_AFFECTED, - ((1 << AudioSystem.STREAM_RING)|(1 << AudioSystem.STREAM_NOTIFICATION)| - (1 << AudioSystem.STREAM_SYSTEM)|(1 << AudioSystem.STREAM_SYSTEM_ENFORCED))); - if (mVoiceCapable) { - mRingerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_MUSIC); - } else { - mRingerModeAffectedStreams |= (1 << AudioSystem.STREAM_MUSIC); + // System.VIBRATE_ON is not used any more but defaults for mVibrateSetting + // are still needed while setVibrateSetting() and getVibrateSetting() are being + // deprecated. + mVibrateSetting = getValueForVibrateSetting(0, + AudioManager.VIBRATE_TYPE_NOTIFICATION, + mHasVibrator ? AudioManager.VIBRATE_SETTING_ONLY_SILENT + : AudioManager.VIBRATE_SETTING_OFF); + mVibrateSetting = getValueForVibrateSetting(mVibrateSetting, + AudioManager.VIBRATE_TYPE_RINGER, + mHasVibrator ? AudioManager.VIBRATE_SETTING_ONLY_SILENT + : AudioManager.VIBRATE_SETTING_OFF); + + // make sure settings for ringer mode are consistent with device type: non voice capable + // devices (tablets) include media stream in silent mode whereas phones don't. + mRingerModeAffectedStreams = Settings.System.getIntForUser(cr, + Settings.System.MODE_RINGER_STREAMS_AFFECTED, + ((1 << AudioSystem.STREAM_RING)|(1 << AudioSystem.STREAM_NOTIFICATION)| + (1 << AudioSystem.STREAM_SYSTEM)|(1 << AudioSystem.STREAM_SYSTEM_ENFORCED)), + UserHandle.USER_CURRENT); + + // ringtone, notification and system streams are always affected by ringer mode + mRingerModeAffectedStreams |= (1 << AudioSystem.STREAM_RING)| + (1 << AudioSystem.STREAM_NOTIFICATION)| + (1 << AudioSystem.STREAM_SYSTEM); + + if (mVoiceCapable) { + mRingerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_MUSIC); + } else { + mRingerModeAffectedStreams |= (1 << AudioSystem.STREAM_MUSIC); + } + synchronized (mCameraSoundForced) { + if (mCameraSoundForced) { + mRingerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED); + } else { + mRingerModeAffectedStreams |= (1 << AudioSystem.STREAM_SYSTEM_ENFORCED); + } + } + + Settings.System.putIntForUser(cr, + Settings.System.MODE_RINGER_STREAMS_AFFECTED, + mRingerModeAffectedStreams, + UserHandle.USER_CURRENT); + + readDockAudioSettings(cr); } - Settings.System.putInt(cr, - Settings.System.MODE_RINGER_STREAMS_AFFECTED, mRingerModeAffectedStreams); - mMuteAffectedStreams = System.getInt(cr, + mMuteAffectedStreams = System.getIntForUser(cr, System.MUTE_STREAMS_AFFECTED, - ((1 << AudioSystem.STREAM_MUSIC)|(1 << AudioSystem.STREAM_RING)|(1 << AudioSystem.STREAM_SYSTEM))); + ((1 << AudioSystem.STREAM_MUSIC)| + (1 << AudioSystem.STREAM_RING)| + (1 << AudioSystem.STREAM_SYSTEM)), + UserHandle.USER_CURRENT); - boolean masterMute = System.getInt(cr, System.VOLUME_MASTER_MUTE, 0) == 1; + boolean masterMute = System.getIntForUser(cr, System.VOLUME_MASTER_MUTE, + 0, UserHandle.USER_CURRENT) == 1; AudioSystem.setMasterMute(masterMute); broadcastMasterMuteStatus(masterMute); @@ -710,7 +796,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { if (streamType == STREAM_REMOTE_MUSIC) { // don't play sounds for remote - flags &= ~AudioManager.FLAG_PLAY_SOUND; + flags &= ~(AudioManager.FLAG_PLAY_SOUND|AudioManager.FLAG_FIXED_VOLUME); //if (DEBUG_VOL) Log.i(TAG, "Need to adjust remote volume: calling adjustRemoteVolume()"); adjustRemoteVolume(AudioSystem.STREAM_MUSIC, direction, flags); } else { @@ -740,58 +826,72 @@ public class AudioService extends IAudioService.Stub implements OnFinished { // convert one UI step (+/-1) into a number of internal units on the stream alias int step = rescaleIndex(10, streamType, streamTypeAlias); - // If either the client forces allowing ringer modes for this adjustment, - // or the stream type is one that is affected by ringer modes - if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) || - (streamTypeAlias == getMasterStreamType())) { - int ringerMode = getRingerMode(); - // do not vibrate if already in vibrate mode - if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) { - flags &= ~AudioManager.FLAG_VIBRATE; - } - // Check if the ringer mode changes with this volume adjustment. If - // it does, it will handle adjusting the volume, so we won't below - adjustVolume = checkForRingerModeChange(aliasIndex, direction, step); - if ((streamTypeAlias == getMasterStreamType()) && - (mRingerMode == AudioManager.RINGER_MODE_SILENT)) { - streamState.setLastAudibleIndex(0, device); - } + if ((direction == AudioManager.ADJUST_RAISE) && + !checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) { + return; } - // If stream is muted, adjust last audible index only int index; - final int oldIndex = mStreamStates[streamType].getIndex(device, - (mStreamStates[streamType].muteCount() != 0) /* lastAudible */); - - if (streamState.muteCount() != 0) { - if (adjustVolume) { - // Post a persist volume msg - // no need to persist volume on all streams sharing the same alias - streamState.adjustLastAudibleIndex(direction * step, device); - sendMsg(mAudioHandler, - MSG_PERSIST_VOLUME, - SENDMSG_QUEUE, - PERSIST_LAST_AUDIBLE, - device, - streamState, - PERSIST_DELAY); - } - index = mStreamStates[streamType].getIndex(device, true /* lastAudible */); + int oldIndex; + + flags &= ~AudioManager.FLAG_FIXED_VOLUME; + if ((streamTypeAlias == AudioSystem.STREAM_MUSIC) && + ((device & mFixedVolumeDevices) != 0)) { + flags |= AudioManager.FLAG_FIXED_VOLUME; + index = mStreamStates[streamType].getMaxIndex(); + oldIndex = index; } else { - if (adjustVolume && streamState.adjustIndex(direction * step, device)) { - // Post message to set system volume (it in turn will post a message - // to persist). Do not change volume if stream is muted. - sendMsg(mAudioHandler, - MSG_SET_DEVICE_VOLUME, - SENDMSG_QUEUE, - device, - 0, - streamState, - 0); + // If either the client forces allowing ringer modes for this adjustment, + // or the stream type is one that is affected by ringer modes + if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) || + (streamTypeAlias == getMasterStreamType())) { + int ringerMode = getRingerMode(); + // do not vibrate if already in vibrate mode + if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) { + flags &= ~AudioManager.FLAG_VIBRATE; + } + // Check if the ringer mode changes with this volume adjustment. If + // it does, it will handle adjusting the volume, so we won't below + adjustVolume = checkForRingerModeChange(aliasIndex, direction, step); + if ((streamTypeAlias == getMasterStreamType()) && + (mRingerMode == AudioManager.RINGER_MODE_SILENT)) { + streamState.setLastAudibleIndex(0, device); + } } - index = mStreamStates[streamType].getIndex(device, false /* lastAudible */); - } + // If stream is muted, adjust last audible index only + oldIndex = mStreamStates[streamType].getIndex(device, + (mStreamStates[streamType].muteCount() != 0) /* lastAudible */); + + if (streamState.muteCount() != 0) { + if (adjustVolume) { + // Post a persist volume msg + // no need to persist volume on all streams sharing the same alias + streamState.adjustLastAudibleIndex(direction * step, device); + sendMsg(mAudioHandler, + MSG_PERSIST_VOLUME, + SENDMSG_QUEUE, + PERSIST_LAST_AUDIBLE, + device, + streamState, + PERSIST_DELAY); + } + index = mStreamStates[streamType].getIndex(device, true /* lastAudible */); + } else { + if (adjustVolume && streamState.adjustIndex(direction * step, device)) { + // Post message to set system volume (it in turn will post a message + // to persist). Do not change volume if stream is muted. + sendMsg(mAudioHandler, + MSG_SET_DEVICE_VOLUME, + SENDMSG_QUEUE, + device, + 0, + streamState, + 0); + } + index = mStreamStates[streamType].getIndex(device, false /* lastAudible */); + } + } sendVolumeUpdate(streamType, oldIndex, index, flags); } @@ -817,35 +917,48 @@ public class AudioService extends IAudioService.Stub implements OnFinished { VolumeStreamState streamState = mStreamStates[mStreamVolumeAlias[streamType]]; final int device = getDeviceForStream(streamType); - // get last audible index if stream is muted, current index otherwise - final int oldIndex = streamState.getIndex(device, - (streamState.muteCount() != 0) /* lastAudible */); + int oldIndex; + + flags &= ~AudioManager.FLAG_FIXED_VOLUME; + if ((mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC) && + ((device & mFixedVolumeDevices) != 0)) { + flags |= AudioManager.FLAG_FIXED_VOLUME; + index = mStreamStates[streamType].getMaxIndex(); + oldIndex = index; + } else { + // get last audible index if stream is muted, current index otherwise + oldIndex = streamState.getIndex(device, + (streamState.muteCount() != 0) /* lastAudible */); - index = rescaleIndex(index * 10, streamType, mStreamVolumeAlias[streamType]); - - // setting volume on master stream type also controls silent mode - if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) || - (mStreamVolumeAlias[streamType] == getMasterStreamType())) { - int newRingerMode; - if (index == 0) { - newRingerMode = mHasVibrator ? AudioManager.RINGER_MODE_VIBRATE - : AudioManager.RINGER_MODE_SILENT; - setStreamVolumeInt(mStreamVolumeAlias[streamType], - index, - device, - false, - true); - } else { - newRingerMode = AudioManager.RINGER_MODE_NORMAL; + index = rescaleIndex(index * 10, streamType, mStreamVolumeAlias[streamType]); + + if (!checkSafeMediaVolume(mStreamVolumeAlias[streamType], index, device)) { + return; } - setRingerMode(newRingerMode); - } - setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, false, true); - // get last audible index if stream is muted, current index otherwise - index = mStreamStates[streamType].getIndex(device, - (mStreamStates[streamType].muteCount() != 0) /* lastAudible */); + // setting volume on master stream type also controls silent mode + if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) || + (mStreamVolumeAlias[streamType] == getMasterStreamType())) { + int newRingerMode; + if (index == 0) { + newRingerMode = mHasVibrator ? AudioManager.RINGER_MODE_VIBRATE + : AudioManager.RINGER_MODE_SILENT; + setStreamVolumeInt(mStreamVolumeAlias[streamType], + index, + device, + false, + true); + } else { + newRingerMode = AudioManager.RINGER_MODE_NORMAL; + } + setRingerMode(newRingerMode); + } + setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, false, true); + // get last audible index if stream is muted, current index otherwise + index = mStreamStates[streamType].getIndex(device, + (mStreamStates[streamType].muteCount() != 0) /* lastAudible */); + } sendVolumeUpdate(streamType, oldIndex, index, flags); } @@ -935,6 +1048,24 @@ public class AudioService extends IAudioService.Stub implements OnFinished { return delta; } + private void sendBroadcastToAll(Intent intent) { + final long ident = Binder.clearCallingIdentity(); + try { + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void sendStickyBroadcastToAll(Intent intent) { + final long ident = Binder.clearCallingIdentity(); + try { + mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + // UI update and Broadcast Intent private void sendVolumeUpdate(int streamType, int oldIndex, int index, int flags) { if (!mVoiceCapable && (streamType == AudioSystem.STREAM_RING)) { @@ -943,13 +1074,15 @@ public class AudioService extends IAudioService.Stub implements OnFinished { mVolumePanel.postVolumeChanged(streamType, flags); - oldIndex = (oldIndex + 5) / 10; - index = (index + 5) / 10; - Intent intent = new Intent(AudioManager.VOLUME_CHANGED_ACTION); - intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, streamType); - intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, index); - intent.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, oldIndex); - mContext.sendBroadcast(intent); + if ((flags & AudioManager.FLAG_FIXED_VOLUME) == 0) { + oldIndex = (oldIndex + 5) / 10; + index = (index + 5) / 10; + Intent intent = new Intent(AudioManager.VOLUME_CHANGED_ACTION); + intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, streamType); + intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, index); + intent.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, oldIndex); + sendBroadcastToAll(intent); + } } // UI update and Broadcast Intent @@ -959,7 +1092,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { Intent intent = new Intent(AudioManager.MASTER_VOLUME_CHANGED_ACTION); intent.putExtra(AudioManager.EXTRA_PREV_MASTER_VOLUME_VALUE, oldVolume); intent.putExtra(AudioManager.EXTRA_MASTER_VOLUME_VALUE, newVolume); - mContext.sendBroadcast(intent); + sendBroadcastToAll(intent); } // UI update and Broadcast Intent @@ -973,9 +1106,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { intent.putExtra(AudioManager.EXTRA_MASTER_VOLUME_MUTED, muted); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | Intent.FLAG_RECEIVER_REPLACE_PENDING); - long origCallerIdentityToken = Binder.clearCallingIdentity(); - mContext.sendStickyBroadcast(intent); - Binder.restoreCallingIdentity(origCallerIdentityToken); + sendStickyBroadcastToAll(intent); } /** @@ -1066,7 +1197,15 @@ public class AudioService extends IAudioService.Stub implements OnFinished { public int getStreamVolume(int streamType) { ensureValidStreamType(streamType); int device = getDeviceForStream(streamType); - return (mStreamStates[streamType].getIndex(device, false /* lastAudible */) + 5) / 10; + int index; + + if ((mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC) && + (device & mFixedVolumeDevices) != 0) { + index = mStreamStates[streamType].getMaxIndex(); + } else { + index = mStreamStates[streamType].getIndex(device, false /* lastAudible */); + } + return (index + 5) / 10; } public int getMasterVolume() { @@ -1206,8 +1345,8 @@ public class AudioService extends IAudioService.Stub implements OnFinished { private void restoreMasterVolume() { if (mUseMasterVolume) { - float volume = Settings.System.getFloat(mContentResolver, - Settings.System.VOLUME_MASTER, -1.0f); + float volume = Settings.System.getFloatForUser(mContentResolver, + Settings.System.VOLUME_MASTER, -1.0f, UserHandle.USER_CURRENT); if (volume >= 0.0f) { AudioSystem.setMasterVolume(volume); } @@ -1643,6 +1782,10 @@ public class AudioService extends IAudioService.Stub implements OnFinished { /** @see AudioManager#reloadAudioSettings() */ public void reloadAudioSettings() { + readAudioSettings(false /*userSwitch*/); + } + + private void readAudioSettings(boolean userSwitch) { // restore ringer mode, ringer mode affected streams, mute affected streams and vibrate settings readPersistedSettings(); @@ -1651,11 +1794,16 @@ public class AudioService extends IAudioService.Stub implements OnFinished { for (int streamType = 0; streamType < numStreamTypes; streamType++) { VolumeStreamState streamState = mStreamStates[streamType]; + if (userSwitch && mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC) { + continue; + } + synchronized (streamState) { streamState.readSettings(); // unmute stream that was muted but is not affect by mute anymore - if (streamState.muteCount() != 0 && !isStreamAffectedByMute(streamType)) { + if (streamState.muteCount() != 0 && !isStreamAffectedByMute(streamType) && + !isStreamMutedByRingerMode(streamType)) { int size = streamState.mDeathHandlers.size(); for (int i = 0; i < size; i++) { streamState.mDeathHandlers.get(i).mMuteCount = 1; @@ -1665,10 +1813,17 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } } + // apply new ringer mode before checking volume for alias streams so that streams + // muted by ringer mode have the correct volume + setRingerModeInt(getRingerMode(), false); + checkAllAliasStreamVolumes(); - // apply new ringer mode - setRingerModeInt(getRingerMode(), false); + synchronized (mSafeMediaVolumeState) { + if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE) { + enforceSafeMediaVolume(); + } + } } /** @see AudioManager#setSpeakerphoneOn() */ @@ -1707,7 +1862,13 @@ public class AudioService extends IAudioService.Stub implements OnFinished { /** @see AudioManager#setBluetoothA2dpOn() */ public void setBluetoothA2dpOn(boolean on) { - setBluetoothA2dpOnInt(on); + synchronized (mBluetoothA2dpEnabledLock) { + mBluetoothA2dpEnabled = on; + sendMsg(mAudioHandler, MSG_SET_FORCE_BT_A2DP_USE, SENDMSG_QUEUE, + AudioSystem.FOR_MEDIA, + mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP, + null, 0); + } } /** @see AudioManager#isBluetoothA2dpOn() */ @@ -1991,7 +2152,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, state); newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE, mScoConnectionState); - mContext.sendStickyBroadcast(newIntent); + sendStickyBroadcastToAll(newIntent); mScoConnectionState = state; } } @@ -2091,6 +2252,75 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } }; + /** see AudioManager.setRemoteSubmixOn(boolean on) */ + public void setRemoteSubmixOn(boolean on, int address) { + sendMsg(mAudioHandler, MSG_SET_RSX_CONNECTION_STATE, + SENDMSG_REPLACE /* replace with QUEUE when multiple addresses are supported */, + on ? 1 : 0 /*arg1*/, + address /*arg2*/, + null/*obj*/, 0/*delay*/); + } + + private void onSetRsxConnectionState(int available, int address) { + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_REMOTE_SUBMIX, + available == 1 ? + AudioSystem.DEVICE_STATE_AVAILABLE : AudioSystem.DEVICE_STATE_UNAVAILABLE, + String.valueOf(address) /*device_address*/); + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_REMOTE_SUBMIX, + available == 1 ? + AudioSystem.DEVICE_STATE_AVAILABLE : AudioSystem.DEVICE_STATE_UNAVAILABLE, + String.valueOf(address) /*device_address*/); + } + + private void onCheckMusicActive() { + synchronized (mSafeMediaVolumeState) { + if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE) { + int device = getDeviceForStream(AudioSystem.STREAM_MUSIC); + + if ((device & mSafeMediaVolumeDevices) != 0) { + sendMsg(mAudioHandler, + MSG_CHECK_MUSIC_ACTIVE, + SENDMSG_REPLACE, + 0, + 0, + null, + MUSIC_ACTIVE_POLL_PERIOD_MS); + int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(device, + false /*lastAudible*/); + if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0) && + (index > mSafeMediaVolumeIndex)) { + // Approximate cumulative active music time + mMusicActiveMs += MUSIC_ACTIVE_POLL_PERIOD_MS; + if (mMusicActiveMs > UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX) { + setSafeMediaVolumeEnabled(true); + mMusicActiveMs = 0; + mVolumePanel.postDisplaySafeVolumeWarning(); + } + } + } + } + } + } + + private void onConfigureSafeVolume(boolean force) { + synchronized (mSafeMediaVolumeState) { + int mcc = mContext.getResources().getConfiguration().mcc; + if ((mMcc != mcc) || ((mMcc == 0) && force)) { + mSafeMediaVolumeIndex = mContext.getResources().getInteger( + com.android.internal.R.integer.config_safe_media_volume_index) * 10; + boolean safeMediaVolumeEnabled = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_safe_media_volume_enabled); + if (safeMediaVolumeEnabled) { + mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_ACTIVE; + enforceSafeMediaVolume(); + } else { + mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_DISABLED; + } + mMcc = mcc; + } + } + } + /////////////////////////////////////////////////////////////////////////// // Internal methods /////////////////////////////////////////////////////////////////////////// @@ -2223,7 +2453,8 @@ public class AudioService extends IAudioService.Stub implements OnFinished { if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_REMOTE_MUSIC"); return STREAM_REMOTE_MUSIC; - } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)) { + } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, + DEFAULT_STREAM_TYPE_OVERRIDE_DELAY_MS)) { if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC stream active"); return AudioSystem.STREAM_MUSIC; @@ -2252,9 +2483,9 @@ public class AudioService extends IAudioService.Stub implements OnFinished { return AudioSystem.STREAM_VOICE_CALL; } } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_NOTIFICATION, - NOTIFICATION_VOLUME_DELAY_MS) || + DEFAULT_STREAM_TYPE_OVERRIDE_DELAY_MS) || AudioSystem.isStreamActive(AudioSystem.STREAM_RING, - NOTIFICATION_VOLUME_DELAY_MS)) { + DEFAULT_STREAM_TYPE_OVERRIDE_DELAY_MS)) { if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_NOTIFICATION"); return AudioSystem.STREAM_NOTIFICATION; } else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) { @@ -2282,9 +2513,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { broadcast.putExtra(AudioManager.EXTRA_RINGER_MODE, ringerMode); broadcast.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | Intent.FLAG_RECEIVER_REPLACE_PENDING); - long origCallerIdentityToken = Binder.clearCallingIdentity(); - mContext.sendStickyBroadcast(broadcast); - Binder.restoreCallingIdentity(origCallerIdentityToken); + sendStickyBroadcastToAll(broadcast); } private void broadcastVibrateSetting(int vibrateType) { @@ -2293,7 +2522,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { Intent broadcast = new Intent(AudioManager.VIBRATE_SETTING_CHANGED_ACTION); broadcast.putExtra(AudioManager.EXTRA_VIBRATE_TYPE, vibrateType); broadcast.putExtra(AudioManager.EXTRA_VIBRATE_SETTING, getVibrateSetting(vibrateType)); - mContext.sendBroadcast(broadcast); + sendBroadcastToAll(broadcast); } } @@ -2403,9 +2632,10 @@ public class AudioService extends IAudioService.Stub implements OnFinished { AudioSystem.initStreamVolume(streamType, 0, mIndexMax); mIndexMax *= 10; - readSettings(); - + // mDeathHandlers must be created before calling readSettings() mDeathHandlers = new ArrayList<VolumeDeathHandler>(); + + readSettings(); } public String getSettingNameForDevice(boolean lastAudible, int device) { @@ -2422,6 +2652,26 @@ public class AudioService extends IAudioService.Stub implements OnFinished { public synchronized void readSettings() { int remainingDevices = AudioSystem.DEVICE_OUT_ALL; + // do not read system stream volume from settings: this stream is always aliased + // to another stream type and its volume is never persisted. Values in settings can + // only be stale values + // on first call to readSettings() at init time, muteCount() is always 0 so we will + // always create entries for default device + if ((mStreamType == AudioSystem.STREAM_SYSTEM) || + (mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED)) { + int index = 10 * AudioManager.DEFAULT_STREAM_VOLUME[mStreamType]; + synchronized (mCameraSoundForced) { + if (mCameraSoundForced) { + index = mIndexMax; + } + } + if (muteCount() == 0) { + mIndex.put(AudioSystem.DEVICE_OUT_DEFAULT, index); + } + mLastAudibleIndex.put(AudioSystem.DEVICE_OUT_DEFAULT, index); + return; + } + for (int i = 0; remainingDevices != 0; i++) { int device = (1 << i); if ((device & remainingDevices) == 0) { @@ -2429,13 +2679,23 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } remainingDevices &= ~device; + // ignore settings for fixed volume devices: volume should always be at max + if ((mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_MUSIC) && + ((device & mFixedVolumeDevices) != 0)) { + if (muteCount() == 0) { + mIndex.put(device, mIndexMax); + } + mLastAudibleIndex.put(device, mIndexMax); + continue; + } // retrieve current volume for device String name = getSettingNameForDevice(false /* lastAudible */, device); // if no volume stored for current stream and device, use default volume if default // device, continue otherwise int defaultIndex = (device == AudioSystem.DEVICE_OUT_DEFAULT) ? AudioManager.DEFAULT_STREAM_VOLUME[mStreamType] : -1; - int index = Settings.System.getInt(mContentResolver, name, defaultIndex); + int index = Settings.System.getIntForUser( + mContentResolver, name, defaultIndex, UserHandle.USER_CURRENT); if (index == -1) { continue; } @@ -2446,15 +2706,13 @@ public class AudioService extends IAudioService.Stub implements OnFinished { // or default index defaultIndex = (index > 0) ? index : AudioManager.DEFAULT_STREAM_VOLUME[mStreamType]; - int lastAudibleIndex = Settings.System.getInt(mContentResolver, name, defaultIndex); + int lastAudibleIndex = Settings.System.getIntForUser( + mContentResolver, name, defaultIndex, UserHandle.USER_CURRENT); // a last audible index of 0 should never be stored for ring and notification // streams on phones (voice capable devices). - // same for system stream on phones and tablets - if ((lastAudibleIndex == 0) && - ((mVoiceCapable && - (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_RING)) || - (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_SYSTEM))) { + if ((lastAudibleIndex == 0) && mVoiceCapable && + (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_RING)) { lastAudibleIndex = AudioManager.DEFAULT_STREAM_VOLUME[mStreamType]; // Correct the data base sendMsg(mAudioHandler, @@ -2468,11 +2726,9 @@ public class AudioService extends IAudioService.Stub implements OnFinished { mLastAudibleIndex.put(device, getValidIndex(10 * lastAudibleIndex)); // the initial index should never be 0 for ring and notification streams on phones // (voice capable devices) if not in silent or vibrate mode. - // same for system stream on phones and tablets if ((index == 0) && (mRingerMode == AudioManager.RINGER_MODE_NORMAL) && - ((mVoiceCapable && - (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_RING)) || - (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_SYSTEM))) { + mVoiceCapable && + (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_RING)) { index = lastAudibleIndex; // Correct the data base sendMsg(mAudioHandler, @@ -2483,7 +2739,9 @@ public class AudioService extends IAudioService.Stub implements OnFinished { this, PERSIST_DELAY); } - mIndex.put(device, getValidIndex(10 * index)); + if (muteCount() == 0) { + mIndex.put(device, getValidIndex(10 * index)); + } } } @@ -2523,6 +2781,11 @@ public class AudioService extends IAudioService.Stub implements OnFinished { public synchronized boolean setIndex(int index, int device, boolean lastAudible) { int oldIndex = getIndex(device, false /* lastAudible */); index = getValidIndex(index); + synchronized (mCameraSoundForced) { + if ((mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED) && mCameraSoundForced) { + index = mIndexMax; + } + } mIndex.put(device, getValidIndex(index)); if (oldIndex != index) { @@ -2617,7 +2880,27 @@ public class AudioService extends IAudioService.Stub implements OnFinished { int device = ((Integer)entry.getKey()).intValue(); int index = ((Integer)entry.getValue()).intValue(); index = rescaleIndex(index, srcStream.getStreamType(), mStreamType); - setIndex(index, device, lastAudible); + + if (lastAudible) { + setLastAudibleIndex(index, device); + } else { + setIndex(index, device, false /* lastAudible */); + } + } + } + + public synchronized void setAllIndexesToMax() { + Set set = mIndex.entrySet(); + Iterator i = set.iterator(); + while (i.hasNext()) { + Map.Entry entry = (Map.Entry)i.next(); + entry.setValue(mIndexMax); + } + set = mLastAudibleIndex.entrySet(); + i = set.iterator(); + while (i.hasNext()) { + Map.Entry entry = (Map.Entry)i.next(); + entry.setValue(mIndexMax); } } @@ -2769,6 +3052,8 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } private void dump(PrintWriter pw) { + pw.print(" Mute count: "); + pw.println(muteCount()); pw.print(" Current: "); Set set = mIndex.entrySet(); Iterator i = set.iterator(); @@ -2858,19 +3143,21 @@ public class AudioService extends IAudioService.Stub implements OnFinished { int persistType, int device) { if ((persistType & PERSIST_CURRENT) != 0) { - System.putInt(mContentResolver, + System.putIntForUser(mContentResolver, streamState.getSettingNameForDevice(false /* lastAudible */, device), - (streamState.getIndex(device, false /* lastAudible */) + 5)/ 10); + (streamState.getIndex(device, false /* lastAudible */) + 5)/ 10, + UserHandle.USER_CURRENT); } if ((persistType & PERSIST_LAST_AUDIBLE) != 0) { - System.putInt(mContentResolver, + System.putIntForUser(mContentResolver, streamState.getSettingNameForDevice(true /* lastAudible */, device), - (streamState.getIndex(device, true /* lastAudible */) + 5) / 10); + (streamState.getIndex(device, true /* lastAudible */) + 5) / 10, + UserHandle.USER_CURRENT); } } private void persistRingerMode(int ringerMode) { - System.putInt(mContentResolver, System.MODE_RINGER, ringerMode); + Settings.Global.putInt(mContentResolver, Settings.Global.MODE_RINGER, ringerMode); } private void playSoundEffect(int effectType, int volume) { @@ -2881,7 +3168,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { float volFloat; // use default if volume is not specified by caller if (volume < 0) { - volFloat = (float)Math.pow(10, SOUND_EFFECT_VOLUME_DB/20); + volFloat = (float)Math.pow(10, (float)sSoundEffectVolumeDb/20); } else { volFloat = (float) volume / 1000.0f; } @@ -2920,8 +3207,10 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } private void onHandlePersistMediaButtonReceiver(ComponentName receiver) { - Settings.System.putString(mContentResolver, Settings.System.MEDIA_BUTTON_RECEIVER, - receiver == null ? "" : receiver.flattenToString()); + Settings.System.putStringForUser(mContentResolver, + Settings.System.MEDIA_BUTTON_RECEIVER, + receiver == null ? "" : receiver.flattenToString(), + UserHandle.USER_CURRENT); } private void cleanupPlayer(MediaPlayer mp) { @@ -2957,13 +3246,17 @@ public class AudioService extends IAudioService.Stub implements OnFinished { break; case MSG_PERSIST_MASTER_VOLUME: - Settings.System.putFloat(mContentResolver, Settings.System.VOLUME_MASTER, - (float)msg.arg1 / (float)1000.0); + Settings.System.putFloatForUser(mContentResolver, + Settings.System.VOLUME_MASTER, + (float)msg.arg1 / (float)1000.0, + UserHandle.USER_CURRENT); break; case MSG_PERSIST_MASTER_VOLUME_MUTE: - Settings.System.putInt(mContentResolver, Settings.System.VOLUME_MASTER_MUTE, - msg.arg1); + Settings.System.putIntForUser(mContentResolver, + Settings.System.VOLUME_MASTER_MUTE, + msg.arg1, + UserHandle.USER_CURRENT); break; case MSG_PERSIST_RINGER_MODE: @@ -3009,6 +3302,8 @@ public class AudioService extends IAudioService.Stub implements OnFinished { // Restore forced usage for communcations and record AudioSystem.setForceUse(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm); AudioSystem.setForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm); + AudioSystem.setForceUse(AudioSystem.FOR_SYSTEM, mCameraSoundForced ? + AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE); // Restore stream volumes int numStreamTypes = AudioSystem.getNumStreamTypes(); @@ -3026,7 +3321,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { restoreMasterVolume(); // Reset device orientation (if monitored for this device) - if (SystemProperties.getBoolean("ro.audio.monitorOrientation", false)) { + if (mMonitorOrientation) { setOrientationForAudioSystem(); } @@ -3055,6 +3350,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { break; case MSG_SET_FORCE_USE: + case MSG_SET_FORCE_BT_A2DP_USE: setForceUse(msg.arg1, msg.arg2); break; @@ -3117,6 +3413,23 @@ public class AudioService extends IAudioService.Stub implements OnFinished { onRegisterVolumeObserverForRcc(msg.arg1 /* rccId */, (IRemoteVolumeObserver)msg.obj /* rvo */); break; + + case MSG_SET_RSX_CONNECTION_STATE: + onSetRsxConnectionState(msg.arg1/*available*/, msg.arg2/*address*/); + break; + + case MSG_CHECK_MUSIC_ACTIVE: + onCheckMusicActive(); + break; + + case MSG_BROADCAST_AUDIO_BECOMING_NOISY: + onSendBecomingNoisyIntent(); + break; + + case MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED: + case MSG_CONFIGURE_SAFE_MEDIA_VOLUME: + onConfigureSafeVolume((msg.what == MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED)); + break; } } } @@ -3127,6 +3440,8 @@ public class AudioService extends IAudioService.Stub implements OnFinished { super(new Handler()); mContentResolver.registerContentObserver(Settings.System.getUriFor( Settings.System.MODE_RINGER_STREAMS_AFFECTED), false, this); + mContentResolver.registerContentObserver(Settings.Global.getUriFor( + Settings.Global.DOCK_AUDIO_MEDIA_ENABLED), false, this); } @Override @@ -3137,15 +3452,23 @@ public class AudioService extends IAudioService.Stub implements OnFinished { // and mRingerModeAffectedStreams, so will leave this synchronized for now. // mRingerModeMutedStreams and mMuteAffectedStreams are safe (only accessed once). synchronized (mSettingsLock) { - int ringerModeAffectedStreams = Settings.System.getInt(mContentResolver, + int ringerModeAffectedStreams = Settings.System.getIntForUser(mContentResolver, Settings.System.MODE_RINGER_STREAMS_AFFECTED, ((1 << AudioSystem.STREAM_RING)|(1 << AudioSystem.STREAM_NOTIFICATION)| - (1 << AudioSystem.STREAM_SYSTEM)|(1 << AudioSystem.STREAM_SYSTEM_ENFORCED))); + (1 << AudioSystem.STREAM_SYSTEM)|(1 << AudioSystem.STREAM_SYSTEM_ENFORCED)), + UserHandle.USER_CURRENT); if (mVoiceCapable) { ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_MUSIC); } else { ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_MUSIC); } + synchronized (mCameraSoundForced) { + if (mCameraSoundForced) { + ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED); + } else { + ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_SYSTEM_ENFORCED); + } + } if (ringerModeAffectedStreams != mRingerModeAffectedStreams) { /* * Ensure all stream types that should be affected by ringer mode @@ -3154,6 +3477,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { mRingerModeAffectedStreams = ringerModeAffectedStreams; setRingerModeInt(getRingerMode(), false); } + readDockAudioSettings(mContentResolver); } } } @@ -3172,8 +3496,8 @@ public class AudioService extends IAudioService.Stub implements OnFinished { address); } - private void sendBecomingNoisyIntent() { - mContext.sendBroadcast(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY)); + private void onSendBecomingNoisyIntent() { + sendBroadcastToAll(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY)); } // must be called synchronized on mConnectedDevices @@ -3292,7 +3616,9 @@ public class AudioService extends IAudioService.Stub implements OnFinished { // sent if none of these devices is connected. int mBecomingNoisyIntentDevices = AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE | - AudioSystem.DEVICE_OUT_ALL_A2DP; + AudioSystem.DEVICE_OUT_ALL_A2DP | AudioSystem.DEVICE_OUT_AUX_DIGITAL | + AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET | AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET | + AudioSystem.DEVICE_OUT_ALL_USB; // must be called before removing the device from mConnectedDevices private int checkSendBecomingNoisyIntent(int device, int state) { @@ -3305,8 +3631,14 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } } if (devices == device) { + sendMsg(mAudioHandler, + MSG_BROADCAST_AUDIO_BECOMING_NOISY, + SENDMSG_REPLACE, + 0, + 0, + null, + 0); delay = 1000; - sendBecomingNoisyIntent(); } } @@ -3362,7 +3694,12 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } } - ActivityManagerNative.broadcastStickyIntent(intent, null); + final long ident = Binder.clearCallingIdentity(); + try { + ActivityManagerNative.broadcastStickyIntent(intent, null, UserHandle.USER_ALL); + } finally { + Binder.restoreCallingIdentity(ident); + } } private void onSetWiredDeviceConnectionState(int device, int state, String name) @@ -3372,12 +3709,26 @@ public class AudioService extends IAudioService.Stub implements OnFinished { (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE))) { setBluetoothA2dpOnInt(true); } - handleDeviceConnection((state == 1), device, ""); - if ((state != 0) && ((device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) || - (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE))) { - setBluetoothA2dpOnInt(false); + boolean isUsb = ((device & AudioSystem.DEVICE_OUT_ALL_USB) != 0); + handleDeviceConnection((state == 1), device, (isUsb ? name : "")); + if (state != 0) { + if ((device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) || + (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE)) { + setBluetoothA2dpOnInt(false); + } + if ((device & mSafeMediaVolumeDevices) != 0) { + sendMsg(mAudioHandler, + MSG_CHECK_MUSIC_ACTIVE, + SENDMSG_REPLACE, + 0, + 0, + null, + MUSIC_ACTIVE_POLL_PERIOD_MS); + } + } + if (!isUsb) { + sendDeviceConnectionIntent(device, state, name); } - sendDeviceConnectionIntent(device, state, name); } } @@ -3406,7 +3757,13 @@ public class AudioService extends IAudioService.Stub implements OnFinished { config = AudioSystem.FORCE_BT_CAR_DOCK; break; case Intent.EXTRA_DOCK_STATE_LE_DESK: - config = AudioSystem.FORCE_ANALOG_DOCK; + synchronized (mSettingsLock) { + if (mDockAudioMediaEnabled) { + config = AudioSystem.FORCE_ANALOG_DOCK; + } else { + config = AudioSystem.FORCE_NONE; + } + } break; case Intent.EXTRA_DOCK_STATE_HE_DESK: config = AudioSystem.FORCE_DIGITAL_DOCK; @@ -3415,6 +3772,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { default: config = AudioSystem.FORCE_NONE; } + AudioSystem.setForceUse(AudioSystem.FOR_DOCK, config); } else if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) { state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, @@ -3469,7 +3827,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { + (action.equals(Intent.ACTION_USB_AUDIO_ACCESSORY_PLUG) ? "ACTION_USB_AUDIO_ACCESSORY_PLUG" : "ACTION_USB_AUDIO_DEVICE_PLUG") + ", state = " + state + ", card: " + alsaCard + ", device: " + alsaDevice); - handleDeviceConnection((state == 1), device, params); + setWiredDeviceConnectionState(device, state, params); } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) { boolean broadcast = false; int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR; @@ -3514,7 +3872,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate. Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED); newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, scoAudioState); - mContext.sendStickyBroadcast(newIntent); + sendStickyBroadcastToAll(newIntent); } } else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { mBootCompleted = true; @@ -3531,13 +3889,21 @@ public class AudioService extends IAudioService.Stub implements OnFinished { Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED); newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - mContext.sendStickyBroadcast(newIntent); + sendStickyBroadcastToAll(newIntent); BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); if (adapter != null) { adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener, BluetoothProfile.A2DP); } + + sendMsg(mAudioHandler, + MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED, + SENDMSG_REPLACE, + 0, + 0, + null, + SAFE_VOLUME_CONFIGURE_TIMEOUT_MS); } else if (action.equals(Intent.ACTION_PACKAGE_REMOVED)) { if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { // a package is being removed, not replaced @@ -3552,6 +3918,27 @@ public class AudioService extends IAudioService.Stub implements OnFinished { AudioSystem.setParameters("screen_state=off"); } else if (action.equalsIgnoreCase(Intent.ACTION_CONFIGURATION_CHANGED)) { handleConfigurationChanged(context); + } else if (action.equals(Intent.ACTION_USER_SWITCHED)) { + // attempt to stop music playback for background user + sendMsg(mAudioHandler, + MSG_BROADCAST_AUDIO_BECOMING_NOISY, + SENDMSG_REPLACE, + 0, + 0, + null, + 0); + // the current audio focus owner is no longer valid + discardAudioFocusOwner(); + + // load volume settings for new user + readAudioSettings(true /*userSwitch*/); + // preserve STREAM_MUSIC volume from one user to the next. + sendMsg(mAudioHandler, + MSG_SET_ALL_VOLUMES, + SENDMSG_QUEUE, + 0, + 0, + mStreamStates[AudioSystem.STREAM_MUSIC], 0); } } } @@ -3587,6 +3974,32 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } }; + /** + * Discard the current audio focus owner. + * Notify top of audio focus stack that it lost focus (regardless of possibility to reassign + * focus), remove it from the stack, and clear the remote control display. + */ + private void discardAudioFocusOwner() { + synchronized(mAudioFocusLock) { + if (!mFocusStack.empty() && (mFocusStack.peek().mFocusDispatcher != null)) { + // notify the current focus owner it lost focus after removing it from stack + FocusStackEntry focusOwner = mFocusStack.pop(); + try { + focusOwner.mFocusDispatcher.dispatchAudioFocusChange( + AudioManager.AUDIOFOCUS_LOSS, focusOwner.mClientId); + } catch (RemoteException e) { + Log.e(TAG, "Failure to signal loss of audio focus due to "+ e); + e.printStackTrace(); + } + focusOwner.unlinkToDeath(); + // clear RCD + synchronized(mRCStack) { + clearRemoteControlDisplay_syncAfRcs(); + } + } + } + } + private void notifyTopOfAudioFocusStack() { // notify the top of the stack it gained focus if (!mFocusStack.empty() && (mFocusStack.peek().mFocusDispatcher != null)) { @@ -3658,9 +4071,12 @@ public class AudioService extends IAudioService.Stub implements OnFinished { Iterator<FocusStackEntry> stackIterator = mFocusStack.iterator(); while(stackIterator.hasNext()) { FocusStackEntry fse = stackIterator.next(); - pw.println(" source:" + fse.mSourceRef + " -- client: " + fse.mClientId + pw.println(" source:" + fse.mSourceRef + + " -- pack: " + fse.mPackageName + + " -- client: " + fse.mClientId + " -- duration: " + fse.mFocusChangeType - + " -- uid: " + fse.mCallingUid); + + " -- uid: " + fse.mCallingUid + + " -- stream: " + fse.mStreamType); } } } @@ -3921,8 +4337,13 @@ public class AudioService extends IAudioService.Stub implements OnFinished { mMediaEventWakeLock.acquire(); keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED); } - mContext.sendOrderedBroadcast(keyIntent, null, mKeyEventDone, - mAudioHandler, Activity.RESULT_OK, null, null); + final long ident = Binder.clearCallingIdentity(); + try { + mContext.sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL, + null, mKeyEventDone, mAudioHandler, Activity.RESULT_OK, null, null); + } finally { + Binder.restoreCallingIdentity(ident); + } } /** @@ -3955,8 +4376,14 @@ public class AudioService extends IAudioService.Stub implements OnFinished { if (needWakeLock) { keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED); } - mContext.sendOrderedBroadcast(keyIntent, null, mKeyEventDone, - mAudioHandler, Activity.RESULT_OK, null, null); + final long ident = Binder.clearCallingIdentity(); + try { + mContext.sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL, + null, mKeyEventDone, + mAudioHandler, Activity.RESULT_OK, null, null); + } finally { + Binder.restoreCallingIdentity(ident); + } } } } @@ -4020,7 +4447,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { startVoiceBasedInteractions(needWakeLock); break; case VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS: - if (DEBUG_RC) Log.v(TAG, " send simulated key event"); + if (DEBUG_RC) Log.v(TAG, " send simulated key event, wakelock=" + needWakeLock); sendSimulatedMediaButtonEvent(keyEvent, needWakeLock); break; } @@ -4328,6 +4755,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { while(stackIterator.hasNext()) { RemoteControlStackEntry rcse = stackIterator.next(); pw.println(" pi: " + rcse.mMediaIntent + + " -- pack: " + rcse.mCallingPackageName + " -- ercvr: " + rcse.mReceiverComponent + " -- client: " + rcse.mRcClient + " -- uid: " + rcse.mCallingUid + @@ -4356,7 +4784,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { " -- vol: " + rcse.mPlaybackVolume + " -- volMax: " + rcse.mPlaybackVolumeMax + " -- volObs: " + rcse.mRemoteVolumeObs); - + } } synchronized (mMainRemote) { @@ -4414,8 +4842,8 @@ public class AudioService extends IAudioService.Stub implements OnFinished { * Restore remote control receiver from the system settings. */ private void restoreMediaButtonReceiver() { - String receiverName = Settings.System.getString(mContentResolver, - Settings.System.MEDIA_BUTTON_RECEIVER); + String receiverName = Settings.System.getStringForUser(mContentResolver, + Settings.System.MEDIA_BUTTON_RECEIVER, UserHandle.USER_CURRENT); if ((null != receiverName) && !receiverName.isEmpty()) { ComponentName eventReceiver = ComponentName.unflattenFromString(receiverName); // construct a PendingIntent targeted to the restored component name @@ -4652,17 +5080,40 @@ public class AudioService extends IAudioService.Stub implements OnFinished { clearRemoteControlDisplay_syncAfRcs(); return; } - // if the top of the two stacks belong to different packages, there is a mismatch, clear + + // determine which entry in the AudioFocus stack to consider, and compare against the + // top of the stack for the media button event receivers : simply using the top of the + // stack would make the entry disappear from the RemoteControlDisplay in conditions such as + // notifications playing during music playback. + // crawl the AudioFocus stack until an entry is found with the following characteristics: + // - focus gain on STREAM_MUSIC stream + // - non-transient focus gain on a stream other than music + FocusStackEntry af = null; + Iterator<FocusStackEntry> stackIterator = mFocusStack.iterator(); + while(stackIterator.hasNext()) { + FocusStackEntry fse = (FocusStackEntry)stackIterator.next(); + if ((fse.mStreamType == AudioManager.STREAM_MUSIC) + || (fse.mFocusChangeType == AudioManager.AUDIOFOCUS_GAIN)) { + af = fse; + break; + } + } + if (af == null) { + clearRemoteControlDisplay_syncAfRcs(); + return; + } + + // if the audio focus and RC owners belong to different packages, there is a mismatch, clear if ((mRCStack.peek().mCallingPackageName != null) - && (mFocusStack.peek().mPackageName != null) + && (af.mPackageName != null) && !(mRCStack.peek().mCallingPackageName.compareTo( - mFocusStack.peek().mPackageName) == 0)) { + af.mPackageName) == 0)) { clearRemoteControlDisplay_syncAfRcs(); return; } // if the audio focus didn't originate from the same Uid as the one in which the remote // control information will be retrieved, clear - if (mRCStack.peek().mCallingUid != mFocusStack.peek().mCallingUid) { + if (mRCStack.peek().mCallingUid != af.mCallingUid) { clearRemoteControlDisplay_syncAfRcs(); return; } @@ -5258,10 +5709,62 @@ public class AudioService extends IAudioService.Stub implements OnFinished { try { // reading new orientation "safely" (i.e. under try catch) in case anything // goes wrong when obtaining resources and configuration - int newOrientation = context.getResources().getConfiguration().orientation; - if (newOrientation != mDeviceOrientation) { - mDeviceOrientation = newOrientation; - setOrientationForAudioSystem(); + Configuration config = context.getResources().getConfiguration(); + if (mMonitorOrientation) { + int newOrientation = config.orientation; + if (newOrientation != mDeviceOrientation) { + mDeviceOrientation = newOrientation; + setOrientationForAudioSystem(); + } + } + sendMsg(mAudioHandler, + MSG_CONFIGURE_SAFE_MEDIA_VOLUME, + SENDMSG_REPLACE, + 0, + 0, + null, + 0); + + boolean cameraSoundForced = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_camera_sound_forced); + synchronized (mSettingsLock) { + synchronized (mCameraSoundForced) { + if (cameraSoundForced != mCameraSoundForced) { + mCameraSoundForced = cameraSoundForced; + + VolumeStreamState s = mStreamStates[AudioSystem.STREAM_SYSTEM_ENFORCED]; + if (cameraSoundForced) { + s.setAllIndexesToMax(); + mRingerModeAffectedStreams &= + ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED); + } else { + s.setAllIndexes(mStreamStates[AudioSystem.STREAM_SYSTEM], + false /*lastAudible*/); + s.setAllIndexes(mStreamStates[AudioSystem.STREAM_SYSTEM], + true /*lastAudible*/); + mRingerModeAffectedStreams |= + (1 << AudioSystem.STREAM_SYSTEM_ENFORCED); + } + // take new state into account for streams muted by ringer mode + setRingerModeInt(getRingerMode(), false); + + sendMsg(mAudioHandler, + MSG_SET_FORCE_USE, + SENDMSG_QUEUE, + AudioSystem.FOR_SYSTEM, + cameraSoundForced ? + AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE, + null, + 0); + + sendMsg(mAudioHandler, + MSG_SET_ALL_VOLUMES, + SENDMSG_QUEUE, + 0, + 0, + mStreamStates[AudioSystem.STREAM_SYSTEM_ENFORCED], 0); + } + } } } catch (Exception e) { Log.e(TAG, "Error retrieving device orientation: " + e); @@ -5296,10 +5799,9 @@ public class AudioService extends IAudioService.Stub implements OnFinished { public void setBluetoothA2dpOnInt(boolean on) { synchronized (mBluetoothA2dpEnabledLock) { mBluetoothA2dpEnabled = on; - sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE, - AudioSystem.FOR_MEDIA, - mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP, - null, 0); + mAudioHandler.removeMessages(MSG_SET_FORCE_BT_A2DP_USE); + AudioSystem.setForceUse(AudioSystem.FOR_MEDIA, + mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP); } } @@ -5323,6 +5825,154 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } } + + //========================================================================================== + // Safe media volume management. + // MUSIC stream volume level is limited when headphones are connected according to safety + // regulation. When the user attempts to raise the volume above the limit, a warning is + // displayed and the user has to acknowlegde before the volume is actually changed. + // The volume index corresponding to the limit is stored in config_safe_media_volume_index + // property. Platforms with a different limit must set this property accordingly in their + // overlay. + //========================================================================================== + + // mSafeMediaVolumeState indicates whether the media volume is limited over headphones. + // It is SAFE_MEDIA_VOLUME_NOT_CONFIGURED at boot time until a network service is connected + // or the configure time is elapsed. It is then set to SAFE_MEDIA_VOLUME_ACTIVE or + // SAFE_MEDIA_VOLUME_DISABLED according to country option. If not SAFE_MEDIA_VOLUME_DISABLED, it + // can be set to SAFE_MEDIA_VOLUME_INACTIVE by calling AudioService.disableSafeMediaVolume() + // (when user opts out). + private final int SAFE_MEDIA_VOLUME_NOT_CONFIGURED = 0; + private final int SAFE_MEDIA_VOLUME_DISABLED = 1; + private final int SAFE_MEDIA_VOLUME_INACTIVE = 2; + private final int SAFE_MEDIA_VOLUME_ACTIVE = 3; + private Integer mSafeMediaVolumeState; + + private int mMcc = 0; + // mSafeMediaVolumeIndex is the cached value of config_safe_media_volume_index property + private int mSafeMediaVolumeIndex; + // mSafeMediaVolumeDevices lists the devices for which safe media volume is enforced, + private final int mSafeMediaVolumeDevices = AudioSystem.DEVICE_OUT_WIRED_HEADSET | + AudioSystem.DEVICE_OUT_WIRED_HEADPHONE; + // mMusicActiveMs is the cumulative time of music activity since safe volume was disabled. + // When this time reaches UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX, the safe media volume is re-enabled + // automatically. mMusicActiveMs is rounded to a multiple of MUSIC_ACTIVE_POLL_PERIOD_MS. + private int mMusicActiveMs; + private static final int UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX = (20 * 3600 * 1000); // 20 hours + private static final int MUSIC_ACTIVE_POLL_PERIOD_MS = 60000; // 1 minute polling interval + private static final int SAFE_VOLUME_CONFIGURE_TIMEOUT_MS = 30000; // 30s after boot completed + + private void setSafeMediaVolumeEnabled(boolean on) { + synchronized (mSafeMediaVolumeState) { + if ((mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_NOT_CONFIGURED) && + (mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_DISABLED)) { + if (on && (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE)) { + mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_ACTIVE; + enforceSafeMediaVolume(); + } else if (!on && (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE)) { + mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE; + mMusicActiveMs = 0; + sendMsg(mAudioHandler, + MSG_CHECK_MUSIC_ACTIVE, + SENDMSG_REPLACE, + 0, + 0, + null, + MUSIC_ACTIVE_POLL_PERIOD_MS); + } + } + } + } + + private void enforceSafeMediaVolume() { + VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC]; + boolean lastAudible = (streamState.muteCount() != 0); + int devices = mSafeMediaVolumeDevices; + int i = 0; + + while (devices != 0) { + int device = 1 << i++; + if ((device & devices) == 0) { + continue; + } + int index = streamState.getIndex(device, lastAudible); + if (index > mSafeMediaVolumeIndex) { + if (lastAudible) { + streamState.setLastAudibleIndex(mSafeMediaVolumeIndex, device); + sendMsg(mAudioHandler, + MSG_PERSIST_VOLUME, + SENDMSG_QUEUE, + PERSIST_LAST_AUDIBLE, + device, + streamState, + PERSIST_DELAY); + } else { + streamState.setIndex(mSafeMediaVolumeIndex, device, true); + sendMsg(mAudioHandler, + MSG_SET_DEVICE_VOLUME, + SENDMSG_QUEUE, + device, + 0, + streamState, + 0); + } + } + devices &= ~device; + } + } + + private boolean checkSafeMediaVolume(int streamType, int index, int device) { + synchronized (mSafeMediaVolumeState) { + if ((mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE) && + (mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC) && + ((device & mSafeMediaVolumeDevices) != 0) && + (index > mSafeMediaVolumeIndex)) { + mVolumePanel.postDisplaySafeVolumeWarning(); + return false; + } + return true; + } + } + + public void disableSafeMediaVolume() { + synchronized (mSafeMediaVolumeState) { + setSafeMediaVolumeEnabled(false); + } + } + + + //========================================================================================== + // Camera shutter sound policy. + // config_camera_sound_forced configuration option in config.xml defines if the camera shutter + // sound is forced (sound even if the device is in silent mode) or not. This option is false by + // default and can be overridden by country specific overlay in values-mccXXX/config.xml. + //========================================================================================== + + // cached value of com.android.internal.R.bool.config_camera_sound_forced + private Boolean mCameraSoundForced; + + // called by android.hardware.Camera to populate CameraInfo.canDisableShutterSound + public boolean isCameraSoundForced() { + synchronized (mCameraSoundForced) { + return mCameraSoundForced; + } + } + + private static final String[] RINGER_MODE_NAMES = new String[] { + "SILENT", + "VIBRATE", + "NORMAL" + }; + + private void dumpRingerMode(PrintWriter pw) { + pw.println("\nRinger mode: "); + pw.println("- mode: "+RINGER_MODE_NAMES[mRingerMode]); + pw.print("- ringer mode affected streams = 0x"); + pw.println(Integer.toHexString(mRingerModeAffectedStreams)); + pw.print("- ringer mode muted streams = 0x"); + pw.println(Integer.toHexString(mRingerModeMutedStreams)); + } + public int verifyX509CertChain(int numcerts, byte [] chain, String domain, String authType) { if (DEBUG_CERTS) { @@ -5368,6 +6018,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { dumpRCStack(pw); dumpRCCStack(pw); dumpStreamStates(pw); + dumpRingerMode(pw); pw.println("\nAudio routes:"); pw.print(" mMainType=0x"); pw.println(Integer.toHexString(mCurAudioRoutes.mMainType)); pw.print(" mBluetoothName="); pw.println(mCurAudioRoutes.mBluetoothName); diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index 1ca0df4..dde2979 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -111,6 +111,13 @@ public class AudioSystem public static native boolean isStreamActive(int stream, int inPastMs); /* + * Checks whether the specified audio source is active. + * + * return true if any recorder using this source is currently recording + */ + public static native boolean isSourceActive(int source); + + /* * Sets a group generic audio configuration parameters. The use of these parameters * are platform dependent, see libaudio * @@ -188,6 +195,13 @@ public class AudioSystem * AudioPolicyService methods */ + // + // audio device definitions: must be kept in sync with values in system/core/audio.h + // + + // reserved bits + public static final int DEVICE_BIT_IN = 0x80000000; + public static final int DEVICE_BIT_DEFAULT = 0x40000000; // output devices, be sure to update AudioManager.java also public static final int DEVICE_OUT_EARPIECE = 0x1; public static final int DEVICE_OUT_SPEAKER = 0x2; @@ -204,8 +218,10 @@ public class AudioSystem public static final int DEVICE_OUT_DGTL_DOCK_HEADSET = 0x1000; public static final int DEVICE_OUT_USB_ACCESSORY = 0x2000; public static final int DEVICE_OUT_USB_DEVICE = 0x4000; + public static final int DEVICE_OUT_REMOTE_SUBMIX = 0x8000; + + public static final int DEVICE_OUT_DEFAULT = DEVICE_BIT_DEFAULT; - public static final int DEVICE_OUT_DEFAULT = 0x8000; public static final int DEVICE_OUT_ALL = (DEVICE_OUT_EARPIECE | DEVICE_OUT_SPEAKER | DEVICE_OUT_WIRED_HEADSET | @@ -221,6 +237,7 @@ public class AudioSystem DEVICE_OUT_DGTL_DOCK_HEADSET | DEVICE_OUT_USB_ACCESSORY | DEVICE_OUT_USB_DEVICE | + DEVICE_OUT_REMOTE_SUBMIX | DEVICE_OUT_DEFAULT); public static final int DEVICE_OUT_ALL_A2DP = (DEVICE_OUT_BLUETOOTH_A2DP | DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES | @@ -232,15 +249,36 @@ public class AudioSystem DEVICE_OUT_USB_DEVICE); // input devices - public static final int DEVICE_IN_COMMUNICATION = 0x10000; - public static final int DEVICE_IN_AMBIENT = 0x20000; - public static final int DEVICE_IN_BUILTIN_MIC1 = 0x40000; - public static final int DEVICE_IN_BUILTIN_MIC2 = 0x80000; - public static final int DEVICE_IN_MIC_ARRAY = 0x100000; - public static final int DEVICE_IN_BLUETOOTH_SCO_HEADSET = 0x200000; - public static final int DEVICE_IN_WIRED_HEADSET = 0x400000; - public static final int DEVICE_IN_AUX_DIGITAL = 0x800000; - public static final int DEVICE_IN_DEFAULT = 0x80000000; + public static final int DEVICE_IN_COMMUNICATION = DEVICE_BIT_IN | 0x1; + public static final int DEVICE_IN_AMBIENT = DEVICE_BIT_IN | 0x2; + public static final int DEVICE_IN_BUILTIN_MIC = DEVICE_BIT_IN | 0x4; + public static final int DEVICE_IN_BLUETOOTH_SCO_HEADSET = DEVICE_BIT_IN | 0x8; + public static final int DEVICE_IN_WIRED_HEADSET = DEVICE_BIT_IN | 0x10; + public static final int DEVICE_IN_AUX_DIGITAL = DEVICE_BIT_IN | 0x20; + public static final int DEVICE_IN_VOICE_CALL = DEVICE_BIT_IN | 0x40; + public static final int DEVICE_IN_BACK_MIC = DEVICE_BIT_IN | 0x80; + public static final int DEVICE_IN_REMOTE_SUBMIX = DEVICE_BIT_IN | 0x100; + public static final int DEVICE_IN_ANLG_DOCK_HEADSET = DEVICE_BIT_IN | 0x200; + public static final int DEVICE_IN_DGTL_DOCK_HEADSET = DEVICE_BIT_IN | 0x400; + public static final int DEVICE_IN_USB_ACCESSORY = DEVICE_BIT_IN | 0x800; + public static final int DEVICE_IN_USB_DEVICE = DEVICE_BIT_IN | 0x1000; + public static final int DEVICE_IN_DEFAULT = DEVICE_BIT_IN | DEVICE_BIT_DEFAULT; + + public static final int DEVICE_IN_ALL = (DEVICE_IN_COMMUNICATION | + DEVICE_IN_AMBIENT | + DEVICE_IN_BUILTIN_MIC | + DEVICE_IN_BLUETOOTH_SCO_HEADSET | + DEVICE_IN_WIRED_HEADSET | + DEVICE_IN_AUX_DIGITAL | + DEVICE_IN_VOICE_CALL | + DEVICE_IN_BACK_MIC | + DEVICE_IN_REMOTE_SUBMIX | + DEVICE_IN_ANLG_DOCK_HEADSET | + DEVICE_IN_DGTL_DOCK_HEADSET | + DEVICE_IN_USB_ACCESSORY | + DEVICE_IN_USB_DEVICE | + DEVICE_IN_DEFAULT); + public static final int DEVICE_IN_ALL_SCO = DEVICE_IN_BLUETOOTH_SCO_HEADSET; // device states, must match AudioSystem::device_connection_state public static final int DEVICE_STATE_UNAVAILABLE = 0; @@ -262,6 +300,7 @@ public class AudioSystem public static final String DEVICE_OUT_DGTL_DOCK_HEADSET_NAME = "digital_dock"; public static final String DEVICE_OUT_USB_ACCESSORY_NAME = "usb_accessory"; public static final String DEVICE_OUT_USB_DEVICE_NAME = "usb_device"; + public static final String DEVICE_OUT_REMOTE_SUBMIX_NAME = "remote_submix"; public static String getDeviceName(int device) { @@ -296,7 +335,9 @@ public class AudioSystem return DEVICE_OUT_USB_ACCESSORY_NAME; case DEVICE_OUT_USB_DEVICE: return DEVICE_OUT_USB_DEVICE_NAME; - case DEVICE_IN_DEFAULT: + case DEVICE_OUT_REMOTE_SUBMIX: + return DEVICE_OUT_REMOTE_SUBMIX_NAME; + case DEVICE_OUT_DEFAULT: default: return ""; } @@ -319,7 +360,8 @@ public class AudioSystem public static final int FORCE_ANALOG_DOCK = 8; public static final int FORCE_DIGITAL_DOCK = 9; public static final int FORCE_NO_BT_A2DP = 10; - private static final int NUM_FORCE_CONFIG = 11; + public static final int FORCE_SYSTEM_ENFORCED = 11; + private static final int NUM_FORCE_CONFIG = 12; public static final int FORCE_DEFAULT = FORCE_NONE; // usage for setForceUse, must match AudioSystem::force_use @@ -327,7 +369,8 @@ public class AudioSystem public static final int FOR_MEDIA = 1; public static final int FOR_RECORD = 2; public static final int FOR_DOCK = 3; - private static final int NUM_FORCE_USE = 4; + public static final int FOR_SYSTEM = 4; + private static final int NUM_FORCE_USE = 5; // usage for AudioRecord.startRecordingSync(), must match AudioSystem::sync_event_t public static final int SYNC_EVENT_NONE = 0; @@ -346,4 +389,9 @@ public class AudioSystem public static native int setMasterMute(boolean mute); public static native boolean getMasterMute(); public static native int getDevicesForStream(int stream); + + // helpers for android.media.AudioManager.getProperty(), see description there for meaning + public static native int getPrimaryOutputSamplingRate(); + public static native int getPrimaryOutputFrameCount(); + } diff --git a/media/java/android/media/DataSource.java b/media/java/android/media/DataSource.java new file mode 100644 index 0000000..347bd5f --- /dev/null +++ b/media/java/android/media/DataSource.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2012 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; + +import java.io.Closeable; + +/** + * An abstraction for a media data source, e.g. a file or an http stream + * {@hide} + */ +public interface DataSource extends Closeable { + /** + * Reads data from the data source at the requested position + * + * @param offset where in the source to read + * @param buffer the buffer to read the data into + * @param size how many bytes to read + * @return the number of bytes read, or -1 if there was an error + */ + public int readAt(long offset, byte[] buffer, int size); + + /** + * Gets the size of the data source. + * + * @return size of data source, or -1 if the length is unknown + */ + public long getSize(); +} diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 7310232..d723cd4 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -110,6 +110,8 @@ interface IAudioService { boolean isBluetoothA2dpOn(); + oneway void setRemoteSubmixOn(boolean on, int address); + int requestAudioFocus(int mainStreamType, int durationHint, IBinder cb, IAudioFocusDispatcher l, String clientId, String callingPackageName); @@ -153,4 +155,6 @@ interface IAudioService { int setBluetoothA2dpDeviceConnectionState(in BluetoothDevice device, int state); AudioRoutesInfo startWatchingRoutes(in IAudioRoutesObserver observer); + + boolean isCameraSoundForced(); } diff --git a/media/java/android/media/IRingtonePlayer.aidl b/media/java/android/media/IRingtonePlayer.aidl index 44a0333..0872f1d 100644 --- a/media/java/android/media/IRingtonePlayer.aidl +++ b/media/java/android/media/IRingtonePlayer.aidl @@ -17,6 +17,7 @@ package android.media; import android.net.Uri; +import android.os.UserHandle; /** * @hide @@ -28,6 +29,6 @@ interface IRingtonePlayer { boolean isPlaying(IBinder token); /** Used for Notification sound playback. */ - void playAsync(in Uri uri, boolean looping, int streamType); + void playAsync(in Uri uri, in UserHandle user, boolean looping, int streamType); void stopAsync(); } diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java index 687d3a5..749ef12 100644 --- a/media/java/android/media/MediaExtractor.java +++ b/media/java/android/media/MediaExtractor.java @@ -22,6 +22,7 @@ import android.content.res.AssetFileDescriptor; import android.media.MediaCodec; import android.media.MediaFormat; import android.net.Uri; + import java.io.FileDescriptor; import java.io.IOException; import java.nio.ByteBuffer; @@ -44,7 +45,7 @@ import java.util.Map; * } * ByteBuffer inputBuffer = ByteBuffer.allocate(...) * while (extractor.readSampleData(inputBuffer, ...) >= 0) { - * int trackIndex = extractor.getTrackIndex(); + * int trackIndex = extractor.getSampleTrackIndex(); * long presentationTimeUs = extractor.getSampleTime(); * ... * extractor.advance(); @@ -60,6 +61,12 @@ final public class MediaExtractor { } /** + * Sets the DataSource object to be used as the data source for this extractor + * {@hide} + */ + public native final void setDataSource(DataSource source); + + /** * Sets the data source as a content Uri. * * @param context the Context to use when resolving the Uri diff --git a/media/java/android/media/MediaFile.java b/media/java/android/media/MediaFile.java index d21ada4..06d43a2 100644 --- a/media/java/android/media/MediaFile.java +++ b/media/java/android/media/MediaFile.java @@ -325,7 +325,7 @@ public class MediaFile { } int lastDot = fileName.lastIndexOf('.'); if (lastDot > 0) { - String extension = fileName.substring(lastDot + 1); + String extension = fileName.substring(lastDot + 1).toUpperCase(); Integer value = sFileTypeToFormatMap.get(extension); if (value != null) { return value.intValue(); diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java index aef631f..cc59d02 100644 --- a/media/java/android/media/MediaMetadataRetriever.java +++ b/media/java/android/media/MediaMetadataRetriever.java @@ -483,5 +483,10 @@ public class MediaMetadataRetriever * of 180 degrees will be retrieved as "-90.0000+180.0000", for instance. */ public static final int METADATA_KEY_LOCATION = 23; + /** + * This key retrieves the video rotation angle in degrees, if available. + * The video rotation angle may be 0, 90, 180, or 270 degrees. + */ + public static final int METADATA_KEY_VIDEO_ROTATION = 24; // Add more here... } diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index cd25865..ef0da3a 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -2265,6 +2265,16 @@ public class MediaPlayer */ public static final int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 200; + /** File or network related operation errors. */ + public static final int MEDIA_ERROR_IO = -1004; + /** Bitstream is not conforming to the related coding standard or file spec. */ + public static final int MEDIA_ERROR_MALFORMED = -1007; + /** Bitstream is conforming to the related coding standard or file spec, but + * the media framework does not support the feature. */ + public static final int MEDIA_ERROR_UNSUPPORTED = -1010; + /** Some operation takes too long to complete, usually more than 3-5 seconds. */ + public static final int MEDIA_ERROR_TIMED_OUT = -110; + /** * Interface definition of a callback to be invoked when there * has been an error during an asynchronous operation (other errors @@ -2282,7 +2292,13 @@ public class MediaPlayer * <li>{@link #MEDIA_ERROR_SERVER_DIED} * </ul> * @param extra an extra code, specific to the error. Typically - * implementation dependant. + * implementation dependent. + * <ul> + * <li>{@link #MEDIA_ERROR_IO} + * <li>{@link #MEDIA_ERROR_MALFORMED} + * <li>{@link #MEDIA_ERROR_UNSUPPORTED} + * <li>{@link #MEDIA_ERROR_TIMED_OUT} + * </ul> * @return True if the method handled the error, false if it didn't. * Returning false, or not having an OnErrorListener at all, will * cause the OnCompletionListener to be called. @@ -2319,6 +2335,11 @@ public class MediaPlayer */ public static final int MEDIA_INFO_STARTED_AS_NEXT = 2; + /** The player just pushed the very first video frame for rendering. + * @see android.media.MediaPlayer.OnInfoListener + */ + public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; + /** The video is too complex for the decoder: it can't decode frames fast * enough. Possibly only the audio plays fine at this stage. * @see android.media.MediaPlayer.OnInfoListener @@ -2374,6 +2395,7 @@ public class MediaPlayer * <ul> * <li>{@link #MEDIA_INFO_UNKNOWN} * <li>{@link #MEDIA_INFO_VIDEO_TRACK_LAGGING} + * <li>{@link #MEDIA_INFO_VIDEO_RENDERING_START} * <li>{@link #MEDIA_INFO_BUFFERING_START} * <li>{@link #MEDIA_INFO_BUFFERING_END} * <li>{@link #MEDIA_INFO_BAD_INTERLEAVING} @@ -2381,7 +2403,7 @@ public class MediaPlayer * <li>{@link #MEDIA_INFO_METADATA_UPDATE} * </ul> * @param extra an extra code, specific to the info. Typically - * implementation dependant. + * implementation dependent. * @return True if the method handled the info, false if it didn't. * Returning false, or not having an OnErrorListener at all, will * cause the info to be discarded. diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index 613354f..48bea52 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -177,6 +177,12 @@ public class MediaRecorder * is applied. */ public static final int VOICE_COMMUNICATION = 7; + + /** + * @hide + * Audio source for remote submix. + */ + public static final int REMOTE_SUBMIX_SOURCE = 8; } /** @@ -291,7 +297,12 @@ public class MediaRecorder * Gets the maximum value for audio sources. * @see android.media.MediaRecorder.AudioSource */ - public static final int getAudioSourceMax() { return AudioSource.VOICE_COMMUNICATION; } + public static final int getAudioSourceMax() { + // FIXME disable selection of the remote submxi source selection once test code + // doesn't rely on it + return AudioSource.REMOTE_SUBMIX_SOURCE; + //return AudioSource.VOICE_COMMUNICATION; + } /** * Sets the video source to be used for recording. If this method is not @@ -720,12 +731,17 @@ public class MediaRecorder public native int getMaxAmplitude() throws IllegalStateException; /* Do not change this value without updating its counterpart - * in include/media/mediarecorder.h! + * in include/media/mediarecorder.h or mediaplayer.h! */ /** Unspecified media recorder error. * @see android.media.MediaRecorder.OnErrorListener */ public static final int MEDIA_RECORDER_ERROR_UNKNOWN = 1; + /** Media server died. In this case, the application must release the + * MediaRecorder object and instantiate a new one. + * @see android.media.MediaRecorder.OnErrorListener + */ + public static final int MEDIA_ERROR_SERVER_DIED = 100; /** * Interface definition for a callback to be invoked when an error @@ -740,6 +756,7 @@ public class MediaRecorder * @param what the type of error that has occurred: * <ul> * <li>{@link #MEDIA_RECORDER_ERROR_UNKNOWN} + * <li>{@link #MEDIA_ERROR_SERVER_DIED} * </ul> * @param extra an extra code, specific to the error type */ diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java index a256079..2a5a16e 100644 --- a/media/java/android/media/MediaRouter.java +++ b/media/java/android/media/MediaRouter.java @@ -22,12 +22,16 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; import android.graphics.drawable.Drawable; +import android.hardware.display.DisplayManager; +import android.hardware.display.WifiDisplay; +import android.hardware.display.WifiDisplayStatus; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; import android.text.TextUtils; import android.util.Log; +import android.view.Display; import java.util.ArrayList; import java.util.HashMap; @@ -48,9 +52,10 @@ import java.util.concurrent.CopyOnWriteArrayList; public class MediaRouter { private static final String TAG = "MediaRouter"; - static class Static { + static class Static implements DisplayManager.DisplayListener { final Resources mResources; final IAudioService mAudioService; + final DisplayManager mDisplayService; final Handler mHandler; final CopyOnWriteArrayList<CallbackInfo> mCallbacks = new CopyOnWriteArrayList<CallbackInfo>(); @@ -60,18 +65,20 @@ public class MediaRouter { final RouteCategory mSystemCategory; - final AudioRoutesInfo mCurRoutesInfo = new AudioRoutesInfo(); + final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo(); - RouteInfo mDefaultAudio; + RouteInfo mDefaultAudioVideo; RouteInfo mBluetoothA2dpRoute; RouteInfo mSelectedRoute; - final IAudioRoutesObserver.Stub mRoutesObserver = new IAudioRoutesObserver.Stub() { + WifiDisplayStatus mLastKnownWifiDisplayStatus; + + final IAudioRoutesObserver.Stub mAudioRoutesObserver = new IAudioRoutesObserver.Stub() { public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) { mHandler.post(new Runnable() { @Override public void run() { - updateRoutes(newRoutes); + updateAudioRoutes(newRoutes); } }); } @@ -84,34 +91,55 @@ public class MediaRouter { IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE); mAudioService = IAudioService.Stub.asInterface(b); + mDisplayService = (DisplayManager) appContext.getSystemService(Context.DISPLAY_SERVICE); + mSystemCategory = new RouteCategory( com.android.internal.R.string.default_audio_route_category_name, - ROUTE_TYPE_LIVE_AUDIO, false); + ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO, false); + mSystemCategory.mIsSystem = true; } // Called after sStatic is initialized void startMonitoringRoutes(Context appContext) { - mDefaultAudio = new RouteInfo(mSystemCategory); - mDefaultAudio.mNameResId = com.android.internal.R.string.default_audio_route_name; - mDefaultAudio.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO; - addRoute(mDefaultAudio); - + mDefaultAudioVideo = new RouteInfo(mSystemCategory); + mDefaultAudioVideo.mNameResId = com.android.internal.R.string.default_audio_route_name; + mDefaultAudioVideo.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO; + mDefaultAudioVideo.mPresentationDisplay = choosePresentationDisplayForRoute( + mDefaultAudioVideo, getAllPresentationDisplays()); + addRouteStatic(mDefaultAudioVideo); + + // This will select the active wifi display route if there is one. + updateWifiDisplayStatus(mDisplayService.getWifiDisplayStatus()); + + appContext.registerReceiver(new WifiDisplayStatusChangedReceiver(), + new IntentFilter(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED)); appContext.registerReceiver(new VolumeChangeReceiver(), new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION)); - AudioRoutesInfo newRoutes = null; + mDisplayService.registerDisplayListener(this, mHandler); + + AudioRoutesInfo newAudioRoutes = null; try { - newRoutes = mAudioService.startWatchingRoutes(mRoutesObserver); + newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver); } catch (RemoteException e) { } - if (newRoutes != null) { - updateRoutes(newRoutes); + if (newAudioRoutes != null) { + // This will select the active BT route if there is one and the current + // selected route is the default system route, or if there is no selected + // route yet. + updateAudioRoutes(newAudioRoutes); + } + + // Select the default route if the above didn't sync us up + // appropriately with relevant system state. + if (mSelectedRoute == null) { + selectRouteStatic(mDefaultAudioVideo.getSupportedTypes(), mDefaultAudioVideo); } } - void updateRoutes(AudioRoutesInfo newRoutes) { - if (newRoutes.mMainType != mCurRoutesInfo.mMainType) { - mCurRoutesInfo.mMainType = newRoutes.mMainType; + void updateAudioRoutes(AudioRoutesInfo newRoutes) { + if (newRoutes.mMainType != mCurAudioRoutesInfo.mMainType) { + mCurAudioRoutesInfo.mMainType = newRoutes.mMainType; int name; if ((newRoutes.mMainType&AudioRoutesInfo.MAIN_HEADPHONES) != 0 || (newRoutes.mMainType&AudioRoutesInfo.MAIN_HEADSET) != 0) { @@ -119,14 +147,16 @@ public class MediaRouter { } else if ((newRoutes.mMainType&AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) { name = com.android.internal.R.string.default_audio_route_name_dock_speakers; } else if ((newRoutes.mMainType&AudioRoutesInfo.MAIN_HDMI) != 0) { - name = com.android.internal.R.string.default_audio_route_name_hdmi; + name = com.android.internal.R.string.default_media_route_name_hdmi; } else { name = com.android.internal.R.string.default_audio_route_name; } - sStatic.mDefaultAudio.mNameResId = name; - dispatchRouteChanged(sStatic.mDefaultAudio); + sStatic.mDefaultAudioVideo.mNameResId = name; + dispatchRouteChanged(sStatic.mDefaultAudioVideo); } + final int mainType = mCurAudioRoutesInfo.mMainType; + boolean a2dpEnabled; try { a2dpEnabled = mAudioService.isBluetoothA2dpOn(); @@ -135,17 +165,17 @@ public class MediaRouter { a2dpEnabled = false; } - if (!TextUtils.equals(newRoutes.mBluetoothName, mCurRoutesInfo.mBluetoothName)) { - mCurRoutesInfo.mBluetoothName = newRoutes.mBluetoothName; - if (mCurRoutesInfo.mBluetoothName != null) { + if (!TextUtils.equals(newRoutes.mBluetoothName, mCurAudioRoutesInfo.mBluetoothName)) { + mCurAudioRoutesInfo.mBluetoothName = newRoutes.mBluetoothName; + if (mCurAudioRoutesInfo.mBluetoothName != null) { if (sStatic.mBluetoothA2dpRoute == null) { final RouteInfo info = new RouteInfo(sStatic.mSystemCategory); - info.mName = mCurRoutesInfo.mBluetoothName; + info.mName = mCurAudioRoutesInfo.mBluetoothName; info.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO; sStatic.mBluetoothA2dpRoute = info; - addRoute(sStatic.mBluetoothA2dpRoute); + addRouteStatic(sStatic.mBluetoothA2dpRoute); } else { - sStatic.mBluetoothA2dpRoute.mName = mCurRoutesInfo.mBluetoothName; + sStatic.mBluetoothA2dpRoute.mName = mCurAudioRoutesInfo.mBluetoothName; dispatchRouteChanged(sStatic.mBluetoothA2dpRoute); } } else if (sStatic.mBluetoothA2dpRoute != null) { @@ -155,15 +185,48 @@ public class MediaRouter { } if (mBluetoothA2dpRoute != null) { - if (mCurRoutesInfo.mMainType != AudioRoutesInfo.MAIN_SPEAKER && - mSelectedRoute == mBluetoothA2dpRoute) { - selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudio); - } else if (mCurRoutesInfo.mMainType == AudioRoutesInfo.MAIN_SPEAKER && - mSelectedRoute == mDefaultAudio && a2dpEnabled) { + if (mainType != AudioRoutesInfo.MAIN_SPEAKER && + mSelectedRoute == mBluetoothA2dpRoute && !a2dpEnabled) { + selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo); + } else if ((mSelectedRoute == mDefaultAudioVideo || mSelectedRoute == null) && + a2dpEnabled) { selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute); } } } + + @Override + public void onDisplayAdded(int displayId) { + updatePresentationDisplays(displayId); + } + + @Override + public void onDisplayChanged(int displayId) { + updatePresentationDisplays(displayId); + } + + @Override + public void onDisplayRemoved(int displayId) { + updatePresentationDisplays(displayId); + } + + public Display[] getAllPresentationDisplays() { + return mDisplayService.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION); + } + + private void updatePresentationDisplays(int changedDisplayId) { + final Display[] displays = getAllPresentationDisplays(); + final int count = mRoutes.size(); + for (int i = 0; i < count; i++) { + final RouteInfo info = mRoutes.get(i); + Display display = choosePresentationDisplayForRoute(info, displays); + if (display != info.mPresentationDisplay + || (display != null && display.getDisplayId() == changedDisplayId)) { + info.mPresentationDisplay = display; + dispatchRoutePresentationDisplayChanged(info); + } + } + } } static Static sStatic; @@ -181,6 +244,23 @@ public class MediaRouter { public static final int ROUTE_TYPE_LIVE_AUDIO = 0x1; /** + * Route type flag for live video. + * + * <p>A device that supports live video routing will allow a mirrored version + * of the device's primary display or a customized + * {@link android.app.Presentation Presentation} to be routed to supported destinations.</p> + * + * <p>Once initiated, display mirroring is transparent to the application. + * While remote routing is active the application may use a + * {@link android.app.Presentation Presentation} to replace the mirrored view + * on the external display with different content.</p> + * + * @see RouteInfo#getPresentationDisplay() + * @see android.app.Presentation + */ + public static final int ROUTE_TYPE_LIVE_VIDEO = 0x2; + + /** * Route type flag for application-specific usage. * * <p>Unlike other media route types, user routes are managed by the application. @@ -198,6 +278,9 @@ public class MediaRouter { if ((types & ROUTE_TYPE_LIVE_AUDIO) != 0) { result.append("ROUTE_TYPE_LIVE_AUDIO "); } + if ((types & ROUTE_TYPE_LIVE_VIDEO) != 0) { + result.append("ROUTE_TYPE_LIVE_VIDEO "); + } if ((types & ROUTE_TYPE_USER) != 0) { result.append("ROUTE_TYPE_USER "); } @@ -219,7 +302,7 @@ public class MediaRouter { * @hide for use by framework routing UI */ public RouteInfo getSystemAudioRoute() { - return sStatic.mDefaultAudio; + return sStatic.mDefaultAudioVideo; } /** @@ -296,7 +379,8 @@ public class MediaRouter { } static void selectRouteStatic(int types, RouteInfo route) { - if (sStatic.mSelectedRoute == route) return; + final RouteInfo oldRoute = sStatic.mSelectedRoute; + if (oldRoute == route) return; if ((route.getSupportedTypes() & types) == 0) { Log.w(TAG, "selectRoute ignored; cannot select route with supported types " + typesToString(route.getSupportedTypes()) + " into route types " + @@ -306,7 +390,7 @@ public class MediaRouter { final RouteInfo btRoute = sStatic.mBluetoothA2dpRoute; if (btRoute != null && (types & ROUTE_TYPE_LIVE_AUDIO) != 0 && - (route == btRoute || route == sStatic.mDefaultAudio)) { + (route == btRoute || route == sStatic.mDefaultAudioVideo)) { try { sStatic.mAudioService.setBluetoothA2dpOn(route == btRoute); } catch (RemoteException e) { @@ -314,10 +398,21 @@ public class MediaRouter { } } - if (sStatic.mSelectedRoute != null) { + final WifiDisplay activeDisplay = + sStatic.mDisplayService.getWifiDisplayStatus().getActiveDisplay(); + final boolean oldRouteHasAddress = oldRoute != null && oldRoute.mDeviceAddress != null; + final boolean newRouteHasAddress = route != null && route.mDeviceAddress != null; + if (activeDisplay != null || oldRouteHasAddress || newRouteHasAddress) { + if (newRouteHasAddress && !matchesDeviceAddress(activeDisplay, route)) { + sStatic.mDisplayService.connectWifiDisplay(route.mDeviceAddress); + } else if (activeDisplay != null && !newRouteHasAddress) { + sStatic.mDisplayService.disconnectWifiDisplay(); + } + } + + if (oldRoute != null) { // TODO filter types properly - dispatchRouteUnselected(types & sStatic.mSelectedRoute.getSupportedTypes(), - sStatic.mSelectedRoute); + dispatchRouteUnselected(types & oldRoute.getSupportedTypes(), oldRoute); } sStatic.mSelectedRoute = route; if (route != null) { @@ -327,6 +422,22 @@ public class MediaRouter { } /** + * Compare the device address of a display and a route. + * Nulls/no device address will match another null/no address. + */ + static boolean matchesDeviceAddress(WifiDisplay display, RouteInfo info) { + final boolean routeHasAddress = info != null && info.mDeviceAddress != null; + if (display == null && !routeHasAddress) { + return true; + } + + if (display != null && routeHasAddress) { + return display.getDeviceAddress().equals(info.mDeviceAddress); + } + return false; + } + + /** * Add an app-specified route for media to the MediaRouter. * App-specified route definitions are created using {@link #createUserRoute(RouteCategory)} * @@ -335,22 +446,21 @@ public class MediaRouter { * @see #removeUserRoute(UserRouteInfo) */ public void addUserRoute(UserRouteInfo info) { - addRoute(info); + addRouteStatic(info); } /** * @hide Framework use only */ public void addRouteInt(RouteInfo info) { - addRoute(info); + addRouteStatic(info); } - static void addRoute(RouteInfo info) { + static void addRouteStatic(RouteInfo info) { final RouteCategory cat = info.getCategory(); if (!sStatic.mCategories.contains(cat)) { sStatic.mCategories.add(cat); } - final boolean onlyRoute = sStatic.mRoutes.isEmpty(); if (cat.isGroupable() && !(info instanceof RouteGroup)) { // Enforce that any added route in a groupable category must be in a group. final RouteGroup group = new RouteGroup(info.getCategory()); @@ -364,10 +474,6 @@ public class MediaRouter { sStatic.mRoutes.add(info); dispatchRouteAdded(info); } - - if (onlyRoute) { - selectRouteStatic(info.getSupportedTypes(), info); - } } /** @@ -419,7 +525,7 @@ public class MediaRouter { if (info == sStatic.mSelectedRoute) { // Removing the currently selected route? Select the default before we remove it. // TODO: Be smarter about the route types here; this selects for all valid. - selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_USER, sStatic.mDefaultAudio); + selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_USER, sStatic.mDefaultAudioVideo); } if (!found) { sStatic.mCategories.remove(removingCat); @@ -444,7 +550,8 @@ public class MediaRouter { if (info == sStatic.mSelectedRoute) { // Removing the currently selected route? Select the default before we remove it. // TODO: Be smarter about the route types here; this selects for all valid. - selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_USER, sStatic.mDefaultAudio); + selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO | ROUTE_TYPE_USER, + sStatic.mDefaultAudioVideo); } if (!found) { sStatic.mCategories.remove(removingCat); @@ -606,23 +713,192 @@ public class MediaRouter { } } + static void dispatchRoutePresentationDisplayChanged(RouteInfo info) { + for (CallbackInfo cbi : sStatic.mCallbacks) { + if ((cbi.type & info.mSupportedTypes) != 0) { + cbi.cb.onRoutePresentationDisplayChanged(cbi.router, info); + } + } + } + static void systemVolumeChanged(int newValue) { final RouteInfo selectedRoute = sStatic.mSelectedRoute; if (selectedRoute == null) return; if (selectedRoute == sStatic.mBluetoothA2dpRoute || - selectedRoute == sStatic.mDefaultAudio) { + selectedRoute == sStatic.mDefaultAudioVideo) { dispatchRouteVolumeChanged(selectedRoute); } else if (sStatic.mBluetoothA2dpRoute != null) { try { dispatchRouteVolumeChanged(sStatic.mAudioService.isBluetoothA2dpOn() ? - sStatic.mBluetoothA2dpRoute : sStatic.mDefaultAudio); + sStatic.mBluetoothA2dpRoute : sStatic.mDefaultAudioVideo); } catch (RemoteException e) { Log.e(TAG, "Error checking Bluetooth A2DP state to report volume change", e); } } else { - dispatchRouteVolumeChanged(sStatic.mDefaultAudio); + dispatchRouteVolumeChanged(sStatic.mDefaultAudioVideo); + } + } + + static void updateWifiDisplayStatus(WifiDisplayStatus newStatus) { + final WifiDisplayStatus oldStatus = sStatic.mLastKnownWifiDisplayStatus; + + // TODO Naive implementation. Make this smarter later. + boolean wantScan = false; + boolean blockScan = false; + WifiDisplay[] oldDisplays = oldStatus != null ? + oldStatus.getRememberedDisplays() : new WifiDisplay[0]; + WifiDisplay[] newDisplays = newStatus.getRememberedDisplays(); + WifiDisplay[] availableDisplays = newStatus.getAvailableDisplays(); + WifiDisplay activeDisplay = newStatus.getActiveDisplay(); + + for (int i = 0; i < newDisplays.length; i++) { + final WifiDisplay d = newDisplays[i]; + final WifiDisplay oldRemembered = findMatchingDisplay(d, oldDisplays); + if (oldRemembered == null) { + addRouteStatic(makeWifiDisplayRoute(d, + findMatchingDisplay(d, availableDisplays) != null)); + wantScan = true; + } else { + final boolean available = findMatchingDisplay(d, availableDisplays) != null; + final RouteInfo route = findWifiDisplayRoute(d); + updateWifiDisplayRoute(route, d, available, newStatus); + } + if (d.equals(activeDisplay)) { + final RouteInfo activeRoute = findWifiDisplayRoute(d); + if (activeRoute != null) { + selectRouteStatic(activeRoute.getSupportedTypes(), activeRoute); + + // Don't scan if we're already connected to a wifi display, + // the scanning process can cause a hiccup with some configurations. + blockScan = true; + } + } + } + for (int i = 0; i < oldDisplays.length; i++) { + final WifiDisplay d = oldDisplays[i]; + final WifiDisplay newDisplay = findMatchingDisplay(d, newDisplays); + if (newDisplay == null) { + removeRoute(findWifiDisplayRoute(d)); + } + } + + if (wantScan && !blockScan) { + sStatic.mDisplayService.scanWifiDisplays(); + } + + sStatic.mLastKnownWifiDisplayStatus = newStatus; + } + + static RouteInfo makeWifiDisplayRoute(WifiDisplay display, boolean available) { + final RouteInfo newRoute = new RouteInfo(sStatic.mSystemCategory); + newRoute.mDeviceAddress = display.getDeviceAddress(); + newRoute.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO; + newRoute.mVolumeHandling = RouteInfo.PLAYBACK_VOLUME_FIXED; + newRoute.mPlaybackType = RouteInfo.PLAYBACK_TYPE_REMOTE; + + newRoute.setStatusCode(available ? + RouteInfo.STATUS_AVAILABLE : RouteInfo.STATUS_CONNECTING); + newRoute.mEnabled = available; + + newRoute.mName = display.getFriendlyDisplayName(); + + newRoute.mPresentationDisplay = choosePresentationDisplayForRoute(newRoute, + sStatic.getAllPresentationDisplays()); + return newRoute; + } + + private static void updateWifiDisplayRoute(RouteInfo route, WifiDisplay display, + boolean available, WifiDisplayStatus wifiDisplayStatus) { + final boolean isScanning = + wifiDisplayStatus.getScanState() == WifiDisplayStatus.SCAN_STATE_SCANNING; + + boolean changed = false; + int newStatus = RouteInfo.STATUS_NONE; + + if (available) { + newStatus = isScanning ? RouteInfo.STATUS_SCANNING : RouteInfo.STATUS_AVAILABLE; + } else { + newStatus = RouteInfo.STATUS_NOT_AVAILABLE; } + + if (display.equals(wifiDisplayStatus.getActiveDisplay())) { + final int activeState = wifiDisplayStatus.getActiveDisplayState(); + switch (activeState) { + case WifiDisplayStatus.DISPLAY_STATE_CONNECTED: + newStatus = RouteInfo.STATUS_NONE; + break; + case WifiDisplayStatus.DISPLAY_STATE_CONNECTING: + newStatus = RouteInfo.STATUS_CONNECTING; + break; + case WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED: + Log.e(TAG, "Active display is not connected!"); + break; + } + } + + final String newName = display.getFriendlyDisplayName(); + if (!route.getName().equals(newName)) { + route.mName = newName; + changed = true; + } + + changed |= route.mEnabled != available; + route.mEnabled = available; + + changed |= route.setStatusCode(newStatus); + + if (changed) { + dispatchRouteChanged(route); + } + + if (!available && route == sStatic.mSelectedRoute) { + // Oops, no longer available. Reselect the default. + final RouteInfo defaultRoute = sStatic.mDefaultAudioVideo; + selectRouteStatic(defaultRoute.getSupportedTypes(), defaultRoute); + } + } + + private static WifiDisplay findMatchingDisplay(WifiDisplay d, WifiDisplay[] displays) { + for (int i = 0; i < displays.length; i++) { + final WifiDisplay other = displays[i]; + if (d.getDeviceAddress().equals(other.getDeviceAddress())) { + return other; + } + } + return null; + } + + private static RouteInfo findWifiDisplayRoute(WifiDisplay d) { + final int count = sStatic.mRoutes.size(); + for (int i = 0; i < count; i++) { + final RouteInfo info = sStatic.mRoutes.get(i); + if (d.getDeviceAddress().equals(info.mDeviceAddress)) { + return info; + } + } + return null; + } + + private static Display choosePresentationDisplayForRoute(RouteInfo route, Display[] displays) { + if ((route.mSupportedTypes & ROUTE_TYPE_LIVE_VIDEO) != 0) { + if (route.mDeviceAddress != null) { + // Find the indicated Wifi display by its address. + for (Display display : displays) { + if (display.getType() == Display.TYPE_WIFI + && route.mDeviceAddress.equals(display.getAddress())) { + return display; + } + } + return null; + } + + if (route == sStatic.mDefaultAudioVideo && displays.length > 0) { + // Choose the first presentation display from the list. + return displays[0]; + } + } + return null; } /** @@ -643,6 +919,19 @@ public class MediaRouter { int mVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING; int mPlaybackStream = AudioManager.STREAM_MUSIC; VolumeCallbackInfo mVcb; + Display mPresentationDisplay; + + String mDeviceAddress; + boolean mEnabled = true; + + // A predetermined connection status that can override mStatus + private int mStatusCode; + + /** @hide */ public static final int STATUS_NONE = 0; + /** @hide */ public static final int STATUS_SCANNING = 1; + /** @hide */ public static final int STATUS_CONNECTING = 2; + /** @hide */ public static final int STATUS_AVAILABLE = 3; + /** @hide */ public static final int STATUS_NOT_AVAILABLE = 4; private Object mTag; @@ -711,6 +1000,41 @@ public class MediaRouter { } /** + * Set this route's status by predetermined status code. If the caller + * should dispatch a route changed event this call will return true; + */ + boolean setStatusCode(int statusCode) { + if (statusCode != mStatusCode) { + mStatusCode = statusCode; + int resId = 0; + switch (statusCode) { + case STATUS_SCANNING: + resId = com.android.internal.R.string.media_route_status_scanning; + break; + case STATUS_CONNECTING: + resId = com.android.internal.R.string.media_route_status_connecting; + break; + case STATUS_AVAILABLE: + resId = com.android.internal.R.string.media_route_status_available; + break; + case STATUS_NOT_AVAILABLE: + resId = com.android.internal.R.string.media_route_status_not_available; + break; + } + mStatus = resId != 0 ? sStatic.mResources.getText(resId) : null; + return true; + } + return false; + } + + /** + * @hide + */ + public int getStatusCode() { + return mStatusCode; + } + + /** * @return A media type flag set describing which types this route supports. */ public int getSupportedTypes() { @@ -866,6 +1190,45 @@ public class MediaRouter { return mVolumeHandling; } + /** + * Gets the {@link Display} that should be used by the application to show + * a {@link android.app.Presentation} on an external display when this route is selected. + * Depending on the route, this may only be valid if the route is currently + * selected. + * <p> + * The preferred presentation display may change independently of the route + * being selected or unselected. For example, the presentation display + * of the default system route may change when an external HDMI display is connected + * or disconnected even though the route itself has not changed. + * </p><p> + * This method may return null if there is no external display associated with + * the route or if the display is not ready to show UI yet. + * </p><p> + * The application should listen for changes to the presentation display + * using the {@link Callback#onRoutePresentationDisplayChanged} callback and + * show or dismiss its {@link android.app.Presentation} accordingly when the display + * becomes available or is removed. + * </p><p> + * This method only makes sense for {@link #ROUTE_TYPE_LIVE_VIDEO live video} routes. + * </p> + * + * @return The preferred presentation display to use when this route is + * selected or null if none. + * + * @see #ROUTE_TYPE_LIVE_VIDEO + * @see android.app.Presentation + */ + public Display getPresentationDisplay() { + return mPresentationDisplay; + } + + /** + * @return true if this route is enabled and may be selected + */ + public boolean isEnabled() { + return mEnabled; + } + void setStatusInt(CharSequence status) { if (!status.equals(mStatus)) { mStatus = status; @@ -881,7 +1244,6 @@ public class MediaRouter { sStatic.mHandler.post(new Runnable() { @Override public void run() { - //Log.d(TAG, "dispatchRemoteVolumeUpdate dir=" + direction + " val=" + value); if (mVcb != null) { if (direction != 0) { mVcb.vcb.onVolumeUpdateRequest(mVcb.route, direction); @@ -901,9 +1263,11 @@ public class MediaRouter { @Override public String toString() { String supportedTypes = typesToString(getSupportedTypes()); - return getClass().getSimpleName() + "{ name=" + getName() + ", status=" + getStatus() + - " category=" + getCategory() + - " supportedTypes=" + supportedTypes + "}"; + return getClass().getSimpleName() + "{ name=" + getName() + + ", status=" + getStatus() + + ", category=" + getCategory() + + ", supportedTypes=" + supportedTypes + + ", presentationDisplay=" + mPresentationDisplay + "}"; } } @@ -1400,6 +1764,7 @@ public class MediaRouter { int mNameResId; int mTypes; final boolean mGroupable; + boolean mIsSystem; RouteCategory(CharSequence name, int types, boolean groupable) { mName = name; @@ -1486,6 +1851,14 @@ public class MediaRouter { return mGroupable; } + /** + * @return true if this is the category reserved for system routes. + * @hide + */ + public boolean isSystem() { + return mIsSystem; + } + public String toString() { return "RouteCategory{ name=" + mName + " types=" + typesToString(mTypes) + " groupable=" + mGroupable + " }"; @@ -1589,6 +1962,21 @@ public class MediaRouter { * @param info The route with altered volume */ public abstract void onRouteVolumeChanged(MediaRouter router, RouteInfo info); + + /** + * Called when a route's presentation display changes. + * <p> + * This method is called whenever the route's presentation display becomes + * available, is removes or has changes to some of its properties (such as its size). + * </p> + * + * @param router the MediaRouter reporting the event + * @param info The route whose presentation display changed + * + * @see RouteInfo#getPresentationDisplay() + */ + public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo info) { + } } /** @@ -1671,7 +2059,6 @@ public class MediaRouter { } static class VolumeChangeReceiver extends BroadcastReceiver { - @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(AudioManager.VOLUME_CHANGED_ACTION)) { @@ -1689,6 +2076,15 @@ public class MediaRouter { } } } + } + static class WifiDisplayStatusChangedReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED)) { + updateWifiDisplayStatus((WifiDisplayStatus) intent.getParcelableExtra( + DisplayManager.EXTRA_WIFI_DISPLAY_STATUS)); + } + } } } diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java index fd37bcf..88cf4ac 100644 --- a/media/java/android/media/MediaScanner.java +++ b/media/java/android/media/MediaScanner.java @@ -314,6 +314,7 @@ public class MediaScanner private int mMtpObjectHandle; private final String mExternalStoragePath; + private final boolean mExternalIsEmulated; /** whether to use bulk inserts or individual inserts for each item */ private static final boolean ENABLE_BULK_INSERTS = true; @@ -392,6 +393,7 @@ public class MediaScanner setDefaultRingtoneFileNames(); mExternalStoragePath = Environment.getExternalStorageDirectory().getAbsolutePath(); + mExternalIsEmulated = Environment.isExternalStorageEmulated(); //mClient.testGenreNameConverter(); } @@ -543,13 +545,28 @@ public class MediaScanner boolean music = (lowpath.indexOf(MUSIC_DIR) > 0) || (!ringtones && !notifications && !alarms && !podcasts); + boolean isaudio = MediaFile.isAudioFileType(mFileType); + boolean isvideo = MediaFile.isVideoFileType(mFileType); + boolean isimage = MediaFile.isImageFileType(mFileType); + + if (isaudio || isvideo || isimage) { + if (mExternalIsEmulated && path.startsWith(mExternalStoragePath)) { + // try to rewrite the path to bypass the sd card fuse layer + String directPath = Environment.getMediaStorageDirectory() + + path.substring(mExternalStoragePath.length()); + File f = new File(directPath); + if (f.exists()) { + path = directPath; + } + } + } + // we only extract metadata for audio and video files - if (MediaFile.isAudioFileType(mFileType) - || MediaFile.isVideoFileType(mFileType)) { + if (isaudio || isvideo) { processFile(path, mimeType, this); } - if (MediaFile.isImageFileType(mFileType)) { + if (isimage) { processImageFile(path); } @@ -972,7 +989,6 @@ public class MediaScanner } values.put(FileColumns.MEDIA_TYPE, mediaType); } - mMediaProvider.update(result, values, null, null); } @@ -1448,24 +1464,42 @@ public class MediaScanner } FileEntry makeEntryFor(String path) { - String key = path; String where; String[] selectionArgs; - if (mCaseInsensitivePaths) { - // the 'like' makes it use the index, the 'lower()' makes it correct - // when the path contains sqlite wildcard characters - where = "_data LIKE ?1 AND lower(_data)=lower(?1)"; - selectionArgs = new String[] { path }; - } else { - where = Files.FileColumns.DATA + "=?"; - selectionArgs = new String[] { path }; - } Cursor c = null; try { + boolean hasWildCards = path.contains("_") || path.contains("%"); + + if (hasWildCards || !mCaseInsensitivePaths) { + // if there are wildcard characters in the path, the "like" match + // will be slow, and it's worth trying an "=" comparison + // first, since in most cases the case will match. + // Also, we shouldn't do a "like" match on case-sensitive filesystems + where = Files.FileColumns.DATA + "=?"; + selectionArgs = new String[] { path }; + } else { + // if there are no wildcard characters in the path, then the "like" + // match will be just as fast as the "=" case, because of the index + where = "_data LIKE ?1 AND lower(_data)=lower(?1)"; + selectionArgs = new String[] { path }; + } c = mMediaProvider.query(mFilesUri, FILES_PRESCAN_PROJECTION, where, selectionArgs, null, null); - if (c.moveToNext()) { + if (!c.moveToFirst() && hasWildCards && mCaseInsensitivePaths) { + // Try again with case-insensitive match. This will be slower, especially + // if the path contains wildcard characters. + // The 'like' makes it use the index, the 'lower()' makes it correct + // when the path contains sqlite wildcard characters, + where = "_data LIKE ?1 AND lower(_data)=lower(?1)"; + selectionArgs = new String[] { path }; + c.close(); + c = mMediaProvider.query(mFilesUri, FILES_PRESCAN_PROJECTION, + where, selectionArgs, null, null); + // TODO update the path in the db with the correct case so the fast + // path works next time? + } + if (c.moveToFirst()) { long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX); int format = c.getInt(FILES_PRESCAN_FORMAT_COLUMN_INDEX); long lastModified = c.getLong(FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX); diff --git a/media/java/android/media/RemoteDisplay.java b/media/java/android/media/RemoteDisplay.java new file mode 100644 index 0000000..b463d26 --- /dev/null +++ b/media/java/android/media/RemoteDisplay.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2012 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; + +import dalvik.system.CloseGuard; + +import android.os.Handler; +import android.view.Surface; + +/** + * Listens for Wifi remote display connections managed by the media server. + * + * @hide + */ +public final class RemoteDisplay { + /* these constants must be kept in sync with IRemoteDisplayClient.h */ + + public static final int DISPLAY_FLAG_SECURE = 1 << 0; + + public static final int DISPLAY_ERROR_UNKOWN = 1; + public static final int DISPLAY_ERROR_CONNECTION_DROPPED = 2; + + private final CloseGuard mGuard = CloseGuard.get(); + private final Listener mListener; + private final Handler mHandler; + + private int mPtr; + + private native int nativeListen(String iface); + private native void nativeDispose(int ptr); + + private RemoteDisplay(Listener listener, Handler handler) { + mListener = listener; + mHandler = handler; + } + + @Override + protected void finalize() throws Throwable { + try { + dispose(true); + } finally { + super.finalize(); + } + } + + /** + * Starts listening for displays to be connected on the specified interface. + * + * @param iface The interface address and port in the form "x.x.x.x:y". + * @param listener The listener to invoke when displays are connected or disconnected. + * @param handler The handler on which to invoke the listener. + */ + public static RemoteDisplay listen(String iface, Listener listener, Handler handler) { + if (iface == null) { + throw new IllegalArgumentException("iface must not be null"); + } + if (listener == null) { + throw new IllegalArgumentException("listener must not be null"); + } + if (handler == null) { + throw new IllegalArgumentException("handler must not be null"); + } + + RemoteDisplay display = new RemoteDisplay(listener, handler); + display.startListening(iface); + return display; + } + + /** + * Disconnects the remote display and stops listening for new connections. + */ + public void dispose() { + dispose(false); + } + + private void dispose(boolean finalized) { + if (mPtr != 0) { + if (mGuard != null) { + if (finalized) { + mGuard.warnIfOpen(); + } else { + mGuard.close(); + } + } + + nativeDispose(mPtr); + mPtr = 0; + } + } + + private void startListening(String iface) { + mPtr = nativeListen(iface); + if (mPtr == 0) { + throw new IllegalStateException("Could not start listening for " + + "remote display connection on \"" + iface + "\""); + } + mGuard.open("dispose"); + } + + // Called from native. + private void notifyDisplayConnected(final Surface surface, + final int width, final int height, final int flags) { + mHandler.post(new Runnable() { + @Override + public void run() { + mListener.onDisplayConnected(surface, width, height, flags); + } + }); + } + + // Called from native. + private void notifyDisplayDisconnected() { + mHandler.post(new Runnable() { + @Override + public void run() { + mListener.onDisplayDisconnected(); + } + }); + } + + // Called from native. + private void notifyDisplayError(final int error) { + mHandler.post(new Runnable() { + @Override + public void run() { + mListener.onDisplayError(error); + } + }); + } + + /** + * Listener invoked when the remote display connection changes state. + */ + public interface Listener { + void onDisplayConnected(Surface surface, int width, int height, int flags); + void onDisplayDisconnected(); + void onDisplayError(int error); + } +} diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java index 23f7b55..f190eb9 100644 --- a/media/java/android/media/Ringtone.java +++ b/media/java/android/media/Ringtone.java @@ -225,8 +225,9 @@ public class Ringtone { mLocalPlayer.start(); } } else if (mAllowRemote) { + final Uri canonicalUri = mUri.getCanonicalUri(); try { - mRemotePlayer.play(mRemoteToken, mUri, mStreamType); + mRemotePlayer.play(mRemoteToken, canonicalUri, mStreamType); } catch (RemoteException e) { Log.w(TAG, "Problem playing ringtone: " + e); } diff --git a/media/java/android/media/videoeditor/MediaArtistNativeHelper.java b/media/java/android/media/videoeditor/MediaArtistNativeHelper.java index 4756078..f4fccbe 100644 --- a/media/java/android/media/videoeditor/MediaArtistNativeHelper.java +++ b/media/java/android/media/videoeditor/MediaArtistNativeHelper.java @@ -3831,6 +3831,7 @@ class MediaArtistNativeHelper { outWidth, outHeight, Bitmap.Config.ARGB_8888); // Copy int[] to IntBuffer + decBuffer.rewind(); decBuffer.put(decArray, 0, thumbnailSize); decBuffer.rewind(); diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java index a0325dd..487585e 100755 --- a/media/java/android/mtp/MtpDatabase.java +++ b/media/java/android/mtp/MtpDatabase.java @@ -84,11 +84,10 @@ public class MtpDatabase { Files.FileColumns._ID, // 0 Files.FileColumns.DATA, // 1 }; - private static final String[] PATH_SIZE_FORMAT_PROJECTION = new String[] { + private static final String[] PATH_FORMAT_PROJECTION = new String[] { Files.FileColumns._ID, // 0 Files.FileColumns.DATA, // 1 - Files.FileColumns.SIZE, // 2 - Files.FileColumns.FORMAT, // 3 + Files.FileColumns.FORMAT, // 2 }; private static final String[] OBJECT_INFO_PROJECTION = new String[] { Files.FileColumns._ID, // 0 @@ -96,15 +95,14 @@ public class MtpDatabase { Files.FileColumns.FORMAT, // 2 Files.FileColumns.PARENT, // 3 Files.FileColumns.DATA, // 4 - Files.FileColumns.SIZE, // 5 - Files.FileColumns.DATE_MODIFIED, // 6 + Files.FileColumns.DATE_MODIFIED, // 5 }; private static final String ID_WHERE = Files.FileColumns._ID + "=?"; private static final String PATH_WHERE = Files.FileColumns.DATA + "=?"; private static final String STORAGE_WHERE = Files.FileColumns.STORAGE_ID + "=?"; - private static final String FORMAT_WHERE = Files.FileColumns.PARENT + "=?"; - private static final String PARENT_WHERE = Files.FileColumns.FORMAT + "=?"; + private static final String FORMAT_WHERE = Files.FileColumns.FORMAT + "=?"; + private static final String PARENT_WHERE = Files.FileColumns.PARENT + "=?"; private static final String STORAGE_FORMAT_WHERE = STORAGE_WHERE + " AND " + Files.FileColumns.FORMAT + "=?"; private static final String STORAGE_PARENT_WHERE = STORAGE_WHERE + " AND " @@ -835,7 +833,7 @@ public class MtpDatabase { } private boolean getObjectInfo(int handle, int[] outStorageFormatParent, - char[] outName, long[] outSizeModified) { + char[] outName, long[] outModified) { Cursor c = null; try { c = mMediaProvider.query(mObjectsUri, OBJECT_INFO_PROJECTION, @@ -856,8 +854,7 @@ public class MtpDatabase { path.getChars(start, end, outName, 0); outName[end - start] = 0; - outSizeModified[0] = c.getLong(5); - outSizeModified[1] = c.getLong(6); + outModified[0] = c.getLong(5); return true; } } catch (RemoteException e) { @@ -881,14 +878,16 @@ public class MtpDatabase { } Cursor c = null; try { - c = mMediaProvider.query(mObjectsUri, PATH_SIZE_FORMAT_PROJECTION, + c = mMediaProvider.query(mObjectsUri, PATH_FORMAT_PROJECTION, ID_WHERE, new String[] { Integer.toString(handle) }, null, null); if (c != null && c.moveToNext()) { String path = c.getString(1); path.getChars(0, path.length(), outFilePath, 0); outFilePath[path.length()] = 0; - outFileLengthFormat[0] = c.getLong(2); - outFileLengthFormat[1] = c.getLong(3); + // File transfers from device to host will likely fail if the size is incorrect. + // So to be safe, use the actual file size here. + outFileLengthFormat[0] = new File(path).length(); + outFileLengthFormat[1] = c.getLong(2); return MtpConstants.RESPONSE_OK; } else { return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; @@ -910,13 +909,13 @@ public class MtpDatabase { Cursor c = null; try { - c = mMediaProvider.query(mObjectsUri, PATH_SIZE_FORMAT_PROJECTION, + c = mMediaProvider.query(mObjectsUri, PATH_FORMAT_PROJECTION, ID_WHERE, new String[] { Integer.toString(handle) }, null, null); if (c != null && c.moveToNext()) { // don't convert to media path here, since we will be matching // against paths in the database matching /data/media path = c.getString(1); - format = c.getInt(3); + format = c.getInt(2); } else { return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; } diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index 4941ae5..f91c9a0 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -370,7 +370,7 @@ static void android_media_MediaCodec_native_configure( sp<ISurfaceTexture> surfaceTexture; if (jsurface != NULL) { - sp<Surface> surface(Surface_getSurface(env, jsurface)); + sp<Surface> surface(android_view_Surface_getSurface(env, jsurface)); if (surface != NULL) { surfaceTexture = surface->getSurfaceTexture(); } else { diff --git a/media/jni/android_media_MediaExtractor.cpp b/media/jni/android_media_MediaExtractor.cpp index 351ff04..23949fa 100644 --- a/media/jni/android_media_MediaExtractor.cpp +++ b/media/jni/android_media_MediaExtractor.cpp @@ -44,6 +44,72 @@ struct fields_t { static fields_t gFields; +class JavaDataSourceBridge : public DataSource { + jmethodID mReadMethod; + jmethodID mGetSizeMethod; + jmethodID mCloseMethod; + jobject mDataSource; + public: + JavaDataSourceBridge(JNIEnv *env, jobject source) { + mDataSource = env->NewGlobalRef(source); + + jclass datasourceclass = env->GetObjectClass(mDataSource); + CHECK(datasourceclass != NULL); + + mReadMethod = env->GetMethodID(datasourceclass, "readAt", "(J[BI)I"); + CHECK(mReadMethod != NULL); + + mGetSizeMethod = env->GetMethodID(datasourceclass, "getSize", "()J"); + CHECK(mGetSizeMethod != NULL); + + mCloseMethod = env->GetMethodID(datasourceclass, "close", "()V"); + CHECK(mCloseMethod != NULL); + } + + ~JavaDataSourceBridge() { + JNIEnv *env = AndroidRuntime::getJNIEnv(); + env->CallVoidMethod(mDataSource, mCloseMethod); + env->DeleteGlobalRef(mDataSource); + } + + virtual status_t initCheck() const { + return OK; + } + + virtual ssize_t readAt(off64_t offset, void* buffer, size_t size) { + JNIEnv *env = AndroidRuntime::getJNIEnv(); + + // XXX could optimize this by reusing the same array + jbyteArray byteArrayObj = env->NewByteArray(size); + env->DeleteLocalRef(env->GetObjectClass(mDataSource)); + env->DeleteLocalRef(env->GetObjectClass(byteArrayObj)); + ssize_t numread = env->CallIntMethod(mDataSource, mReadMethod, offset, byteArrayObj, size); + env->GetByteArrayRegion(byteArrayObj, 0, size, (jbyte*) buffer); + env->DeleteLocalRef(byteArrayObj); + if (env->ExceptionCheck()) { + ALOGW("Exception occurred while reading %d at %lld", size, offset); + LOGW_EX(env); + env->ExceptionClear(); + return -1; + } + return numread; + } + + virtual status_t getSize(off64_t *size) { + JNIEnv *env = AndroidRuntime::getJNIEnv(); + + CHECK(size != NULL); + + int64_t len = env->CallLongMethod(mDataSource, mGetSizeMethod); + if (len < 0) { + *size = ERROR_UNSUPPORTED; + } else { + *size = len; + } + return OK; + } +}; + //////////////////////////////////////////////////////////////////////////////// JMediaExtractor::JMediaExtractor(JNIEnv *env, jobject thiz) @@ -76,6 +142,10 @@ status_t JMediaExtractor::setDataSource(int fd, off64_t offset, off64_t size) { return mImpl->setDataSource(fd, offset, size); } +status_t JMediaExtractor::setDataSource(const sp<DataSource> &datasource) { + return mImpl->setDataSource(datasource); +} + size_t JMediaExtractor::countTracks() const { return mImpl->countTracks(); } @@ -625,6 +695,33 @@ static void android_media_MediaExtractor_setDataSourceFd( } } +static void android_media_MediaExtractor_setDataSourceCallback( + JNIEnv *env, jobject thiz, + jobject callbackObj) { + sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz); + + if (extractor == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + + if (callbackObj == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } + + sp<JavaDataSourceBridge> bridge = new JavaDataSourceBridge(env, callbackObj); + status_t err = extractor->setDataSource(bridge); + + if (err != OK) { + jniThrowException( + env, + "java/io/IOException", + "Failed to instantiate extractor."); + return; + } +} + static jlong android_media_MediaExtractor_getCachedDurationUs( JNIEnv *env, jobject thiz) { sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz); @@ -713,6 +810,9 @@ static JNINativeMethod gMethods[] = { { "setDataSource", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaExtractor_setDataSourceFd }, + { "setDataSource", "(Landroid/media/DataSource;)V", + (void *)android_media_MediaExtractor_setDataSourceCallback }, + { "getCachedDuration", "()J", (void *)android_media_MediaExtractor_getCachedDurationUs }, diff --git a/media/jni/android_media_MediaExtractor.h b/media/jni/android_media_MediaExtractor.h index 2d4627e..03900db 100644 --- a/media/jni/android_media_MediaExtractor.h +++ b/media/jni/android_media_MediaExtractor.h @@ -19,6 +19,7 @@ #include <media/stagefright/foundation/ABase.h> #include <media/stagefright/MediaSource.h> +#include <media/stagefright/DataSource.h> #include <utils/Errors.h> #include <utils/KeyedVector.h> #include <utils/RefBase.h> @@ -39,6 +40,7 @@ struct JMediaExtractor : public RefBase { const KeyedVector<String8, String8> *headers); status_t setDataSource(int fd, off64_t offset, off64_t size); + status_t setDataSource(const sp<DataSource> &source); size_t countTracks() const; status_t getTrackFormat(size_t index, jobject *format) const; diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp index c2a6889..ad536f2 100644 --- a/media/jni/android_media_MediaPlayer.cpp +++ b/media/jni/android_media_MediaPlayer.cpp @@ -271,9 +271,14 @@ setVideoSurface(JNIEnv *env, jobject thiz, jobject jsurface, jboolean mediaPlaye sp<ISurfaceTexture> new_st; if (jsurface) { - sp<Surface> surface(Surface_getSurface(env, jsurface)); + sp<Surface> surface(android_view_Surface_getSurface(env, jsurface)); if (surface != NULL) { new_st = surface->getSurfaceTexture(); + if (new_st == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", + "The surface does not have a binding SurfaceTexture!"); + return; + } new_st->incStrong(thiz); } else { jniThrowException(env, "java/lang/IllegalArgumentException", diff --git a/media/jni/android_mtp_MtpDatabase.cpp b/media/jni/android_mtp_MtpDatabase.cpp index 99e543b..bc65de5 100644 --- a/media/jni/android_mtp_MtpDatabase.cpp +++ b/media/jni/android_mtp_MtpDatabase.cpp @@ -751,13 +751,22 @@ MtpResponseCode MyMtpDatabase::getObjectPropertyList(MtpObjectHandle handle, MtpResponseCode MyMtpDatabase::getObjectInfo(MtpObjectHandle handle, MtpObjectInfo& info) { - char date[20]; + char date[20]; + MtpString path; + int64_t length; + MtpObjectFormat format; + + MtpResponseCode result = getObjectFilePath(handle, path, length, format); + if (result != MTP_RESPONSE_OK) { + return result; + } + info.mCompressedSize = (length > 0xFFFFFFFFLL ? 0xFFFFFFFF : (uint32_t)length); JNIEnv* env = AndroidRuntime::getJNIEnv(); - jboolean result = env->CallBooleanMethod(mDatabase, method_getObjectInfo, - (jint)handle, mIntBuffer, mStringBuffer, mLongBuffer); - if (!result) + if (!env->CallBooleanMethod(mDatabase, method_getObjectInfo, + (jint)handle, mIntBuffer, mStringBuffer, mLongBuffer)) { return MTP_RESPONSE_INVALID_OBJECT_HANDLE; + } jint* intValues = env->GetIntArrayElements(mIntBuffer, 0); info.mStorageID = intValues[0]; @@ -766,9 +775,7 @@ MtpResponseCode MyMtpDatabase::getObjectInfo(MtpObjectHandle handle, env->ReleaseIntArrayElements(mIntBuffer, intValues, 0); jlong* longValues = env->GetLongArrayElements(mLongBuffer, 0); - uint64_t size = longValues[0]; - info.mCompressedSize = (size > 0xFFFFFFFFLL ? 0xFFFFFFFF : size); - info.mDateModified = longValues[1]; + info.mDateModified = longValues[0]; env->ReleaseLongArrayElements(mLongBuffer, longValues, 0); // info.mAssociationType = (format == MTP_FORMAT_ASSOCIATION ? @@ -783,28 +790,23 @@ MtpResponseCode MyMtpDatabase::getObjectInfo(MtpObjectHandle handle, // read EXIF data for thumbnail information if (info.mFormat == MTP_FORMAT_EXIF_JPEG || info.mFormat == MTP_FORMAT_JFIF) { - MtpString path; - int64_t length; - MtpObjectFormat format; - if (getObjectFilePath(handle, path, length, format) == MTP_RESPONSE_OK) { - ResetJpgfile(); - // Start with an empty image information structure. - memset(&ImageInfo, 0, sizeof(ImageInfo)); - ImageInfo.FlashUsed = -1; - ImageInfo.MeteringMode = -1; - ImageInfo.Whitebalance = -1; - strncpy(ImageInfo.FileName, (const char *)path, PATH_MAX); - if (ReadJpegFile((const char*)path, READ_METADATA)) { - Section_t* section = FindSection(M_EXIF); - if (section) { - info.mThumbCompressedSize = ImageInfo.ThumbnailSize; - info.mThumbFormat = MTP_FORMAT_EXIF_JPEG; - info.mImagePixWidth = ImageInfo.Width; - info.mImagePixHeight = ImageInfo.Height; - } + ResetJpgfile(); + // Start with an empty image information structure. + memset(&ImageInfo, 0, sizeof(ImageInfo)); + ImageInfo.FlashUsed = -1; + ImageInfo.MeteringMode = -1; + ImageInfo.Whitebalance = -1; + strncpy(ImageInfo.FileName, (const char *)path, PATH_MAX); + if (ReadJpegFile((const char*)path, READ_METADATA)) { + Section_t* section = FindSection(M_EXIF); + if (section) { + info.mThumbCompressedSize = ImageInfo.ThumbnailSize; + info.mThumbFormat = MTP_FORMAT_EXIF_JPEG; + info.mImagePixWidth = ImageInfo.Width; + info.mImagePixHeight = ImageInfo.Height; } - DiscardData(); } + DiscardData(); } checkAndClearExceptionFromCallback(env, __FUNCTION__); diff --git a/media/mca/effect/java/android/media/effect/effects/BackDropperEffect.java b/media/mca/effect/java/android/media/effect/effects/BackDropperEffect.java index d5c7aaa..f977e60 100644 --- a/media/mca/effect/java/android/media/effect/effects/BackDropperEffect.java +++ b/media/mca/effect/java/android/media/effect/effects/BackDropperEffect.java @@ -91,6 +91,9 @@ public class BackDropperEffect extends FilterGraphEffect { if (parameterKey.equals("source")) { Filter background = mGraph.getFilter("background"); background.setInputValue("sourceUrl", value); + } else if (parameterKey.equals("context")) { + Filter background = mGraph.getFilter("background"); + background.setInputValue("context", value); } } diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/RedEyeFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/RedEyeFilter.java index 3450ef1..8618804 100644 --- a/media/mca/filterpacks/java/android/filterpacks/imageproc/RedEyeFilter.java +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/RedEyeFilter.java @@ -72,9 +72,7 @@ public class RedEyeFilter extends Filter { "void main() {\n" + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + " vec4 mask = texture2D(tex_sampler_1, v_texcoord);\n" + - " gl_FragColor = vec4(mask.a, mask.a, mask.a, 1.0) * intensity + color * (1.0 - intensity);\n" + " if (mask.a > 0.0) {\n" + - " gl_FragColor.r = 0.0;\n" + " float green_blue = color.g + color.b;\n" + " float red_intensity = color.r / green_blue;\n" + " if (red_intensity > intensity) {\n" + @@ -105,8 +103,8 @@ public class RedEyeFilter extends Filter { ShaderProgram shaderProgram = new ShaderProgram(context, mRedEyeShader); shaderProgram.setMaximumTileSize(mTileSize); mProgram = shaderProgram; + mProgram.setHostValue("intensity", DEFAULT_RED_INTENSITY); break; - default: throw new RuntimeException("Filter RedEye does not support frames of " + "target " + target + "!"); @@ -180,8 +178,6 @@ public class RedEyeFilter extends Filter { } private void updateProgramParams() { - mProgram.setHostValue("intensity", DEFAULT_RED_INTENSITY); - if ( mCenters.length % 2 == 1) { throw new RuntimeException("The size of center array must be even."); } diff --git a/media/mca/filterpacks/java/android/filterpacks/videosrc/MediaSource.java b/media/mca/filterpacks/java/android/filterpacks/videosrc/MediaSource.java index 9c40cec..0be6c62 100644 --- a/media/mca/filterpacks/java/android/filterpacks/videosrc/MediaSource.java +++ b/media/mca/filterpacks/java/android/filterpacks/videosrc/MediaSource.java @@ -35,6 +35,7 @@ import android.filterfw.core.ShaderProgram; import android.filterfw.format.ImageFormat; import android.graphics.SurfaceTexture; import android.media.MediaPlayer; +import android.net.Uri; import android.os.ConditionVariable; import android.opengl.Matrix; import android.view.Surface; @@ -64,6 +65,12 @@ public class MediaSource extends Filter { @GenerateFieldPort(name = "sourceAsset", hasDefault = true) private AssetFileDescriptor mSourceAsset = null; + /** The context for the MediaPlayer to resolve the sourceUrl. + * Make sure this is set before the sourceUrl to avoid unexpected result. + * If the sourceUrl is not a content URI, it is OK to keep this as null. */ + @GenerateFieldPort(name = "context", hasDefault = true) + private Context mContext = null; + /** Whether the media source is a URL or an asset file descriptor. Defaults * to false. */ @@ -459,7 +466,11 @@ public class MediaSource extends Filter { try { if (useUrl) { if (mLogVerbose) Log.v(TAG, "Setting MediaPlayer source to URI " + mSourceUrl); - mMediaPlayer.setDataSource(mSourceUrl); + if (mContext == null) { + mMediaPlayer.setDataSource(mSourceUrl); + } else { + mMediaPlayer.setDataSource(mContext, Uri.parse(mSourceUrl.toString())); + } } else { if (mLogVerbose) Log.v(TAG, "Setting MediaPlayer source to asset " + mSourceAsset); mMediaPlayer.setDataSource(mSourceAsset.getFileDescriptor(), mSourceAsset.getStartOffset(), mSourceAsset.getLength()); diff --git a/media/tests/EffectsTest/Android.mk b/media/tests/EffectsTest/Android.mk new file mode 100755 index 0000000..25b4fe4 --- /dev/null +++ b/media/tests/EffectsTest/Android.mk @@ -0,0 +1,10 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_PACKAGE_NAME := EffectsTest + +include $(BUILD_PACKAGE) diff --git a/media/tests/EffectsTest/AndroidManifest.xml b/media/tests/EffectsTest/AndroidManifest.xml new file mode 100755 index 0000000..9b59891 --- /dev/null +++ b/media/tests/EffectsTest/AndroidManifest.xml @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2009 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.effectstest"> + + <uses-permission android:name="android.permission.RECORD_AUDIO" /> + <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> + + <application> + <activity android:label="@string/app_name" + android:name="EffectsTest"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + + <activity android:label="@string/envreverb_test_name" + android:name="EnvReverbTest"> + </activity> + + <activity android:label="@string/presetrvb_test_name" + android:name="PresetReverbTest"> + </activity> + + <activity android:label="@string/equalizer_test_name" + android:name="EqualizerTest"> + </activity> + + <activity android:label="@string/virtualizer_test_name" + android:name="VirtualizerTest"> + </activity> + + <activity android:label="@string/bassboost_test_name" + android:name="BassBoostTest"> + </activity> + + <activity android:label="@string/visualizer_test_name" + android:name="VisualizerTest"> + </activity> + + </application> +</manifest> diff --git a/media/tests/EffectsTest/res/drawable/icon.png b/media/tests/EffectsTest/res/drawable/icon.png Binary files differnew file mode 100755 index 0000000..64e3601 --- /dev/null +++ b/media/tests/EffectsTest/res/drawable/icon.png diff --git a/media/tests/EffectsTest/res/drawable/stop.png b/media/tests/EffectsTest/res/drawable/stop.png Binary files differnew file mode 100755 index 0000000..83f012c --- /dev/null +++ b/media/tests/EffectsTest/res/drawable/stop.png diff --git a/media/tests/EffectsTest/res/layout/bassboosttest.xml b/media/tests/EffectsTest/res/layout/bassboosttest.xml new file mode 100755 index 0000000..ac912c8 --- /dev/null +++ b/media/tests/EffectsTest/res/layout/bassboosttest.xml @@ -0,0 +1,194 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2009 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical"> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <LinearLayout android:id="@+id/bbReleaseLayout" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" > + + <TextView android:id="@+id/bbReleaseText" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1.0" + android:layout_gravity="center_vertical|left" + android:text="@string/effect_release" + style="@android:style/TextAppearance.Medium" /> + + <ToggleButton android:id="@+id/bbReleaseButton" + android:layout_width="wrap_content" + android:layout_height="fill_parent" + android:layout_gravity="center_vertical|right" + android:layout_weight="0.0" /> + + </LinearLayout> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <LinearLayout android:id="@+id/bbControlLayout" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" > + + <TextView android:id="@+id/bbControlText" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1.0" + android:layout_gravity="center_vertical|left" + android:text="@string/effect_control" + style="@android:style/TextAppearance.Medium" /> + + <ToggleButton android:id="@+id/bassboostOnOff" + android:layout_width="wrap_content" + android:layout_height="fill_parent" + android:layout_gravity="center_vertical|right" + android:layout_weight="0.0" /> + + </LinearLayout> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <LinearLayout android:id="@+id/SessionFrame" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" > + + <TextView android:id="@+id/sessionText" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="0.5" + android:layout_gravity="center_vertical|left" + android:text="@string/session" + style="@android:style/TextAppearance.Medium" /> + + <EditText android:id="@+id/sessionEdit" + android:singleLine="true" + android:numeric="integer" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_weight="0.5" + android:layout_gravity="center_vertical|right" /> + </LinearLayout> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <ScrollView + android:layout_width="fill_parent" + android:layout_height="wrap_content" > + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:orientation="vertical" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" + > + + <TextView android:id="@+id/bbStrengthName" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/stength_name" /> + + <LinearLayout android:id="@+id/bbStrengthDesc" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="30dip" + android:layout_marginBottom="10dip" > + + <TextView android:id="@+id/bbStrengthMin" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1" + android:layout_gravity="left" + android:gravity="left"/> + <TextView android:id="@+id/bbStrengthMax" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1" + android:layout_gravity="right" + android:gravity="right"/> + </LinearLayout> + + <SeekBar android:id="@+id/bbStrengthSeekBar" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:max="100" + android:progress="50" + android:layout_marginLeft="10dip" + android:layout_marginRight="30dip" /> + + <TextView android:id="@+id/bbStrengthValue" + android:layout_width="fill_parent" + android:layout_height="wrap_content" /> + + </LinearLayout> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + </LinearLayout> + + </ScrollView> + +</LinearLayout> diff --git a/media/tests/EffectsTest/res/layout/effectstest.xml b/media/tests/EffectsTest/res/layout/effectstest.xml new file mode 100755 index 0000000..9af4eb6 --- /dev/null +++ b/media/tests/EffectsTest/res/layout/effectstest.xml @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2009 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical"> + + <Button android:id="@+id/env_reverb_actvity" + android:layout_width="fill_parent" android:layout_height="wrap_content" + android:text="@string/envreverb_test_name"> + </Button> + + <Button android:id="@+id/preset_reverb_actvity" + android:layout_width="fill_parent" android:layout_height="wrap_content" + android:text="@string/presetrvb_test_name"> + </Button> + + <Button android:id="@+id/equalizer_actvity" + android:layout_width="fill_parent" android:layout_height="wrap_content" + android:text="@string/equalizer_test_name"> + </Button> + + <Button android:id="@+id/virtualizer_actvity" + android:layout_width="fill_parent" android:layout_height="wrap_content" + android:text="@string/virtualizer_test_name"> + </Button> + + <Button android:id="@+id/bassboost_actvity" + android:layout_width="fill_parent" android:layout_height="wrap_content" + android:text="@string/bassboost_test_name"> + </Button> + + <Button android:id="@+id/visualizer_actvity" + android:layout_width="fill_parent" android:layout_height="wrap_content" + android:text="@string/visualizer_test_name"> + </Button> + + <ListView android:id="@+id/effect_list" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:drawSelectorOnTop="false"/> + + +</LinearLayout> diff --git a/media/tests/EffectsTest/res/layout/envreverbtest.xml b/media/tests/EffectsTest/res/layout/envreverbtest.xml new file mode 100755 index 0000000..01c3240 --- /dev/null +++ b/media/tests/EffectsTest/res/layout/envreverbtest.xml @@ -0,0 +1,553 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2009 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical"> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <LinearLayout android:id="@+id/rvbReleaseLayout" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" > + + <TextView android:id="@+id/rvbReleaseText" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1.0" + android:layout_gravity="center_vertical|left" + android:text="@string/effect_release" + style="@android:style/TextAppearance.Medium" /> + + <ToggleButton android:id="@+id/rvbReleaseButton" + android:layout_width="wrap_content" + android:layout_height="fill_parent" + android:layout_gravity="center_vertical|right" + android:layout_weight="0.0" /> + + </LinearLayout> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <LinearLayout android:id="@+id/rvbControlLayout" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" > + + <TextView android:id="@+id/rvbControlText" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1.0" + android:layout_gravity="center_vertical|left" + android:text="@string/effect_control" + style="@android:style/TextAppearance.Medium" /> + + <ToggleButton android:id="@+id/rvbOnOff" + android:layout_width="wrap_content" + android:layout_height="fill_parent" + android:layout_gravity="center_vertical|right" + android:layout_weight="0.0" /> + + </LinearLayout> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <LinearLayout android:id="@+id/auxFrame" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="3dip" + android:layout_marginTop="3dip" + android:layout_marginRight="3dip" + android:layout_marginBottom="3dip" > + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_gravity="left" + android:layout_weight="1.0" + android:orientation="vertical" + android:layout_marginLeft="1dip" + android:layout_marginTop="1dip" + android:layout_marginRight="1dip" + android:layout_marginBottom="1dip" + > + + <LinearLayout android:id="@+id/playPauseFrame" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="1dip" + android:layout_marginTop="1dip" + android:layout_marginRight="3dip" + android:layout_marginBottom="1dip" > + + <ImageButton android:id="@+id/stop1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical|left" + android:layout_weight="0.0" + android:src="@drawable/stop"/> + + <ImageButton android:id="@+id/playPause1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical|center" + android:layout_weight="0.0" + android:src="@android:drawable/ic_media_play"/> + + <ToggleButton android:id="@+id/attachButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical|right" + android:layout_weight="0.0" + android:textOff="@string/effect_attach_off" + android:textOn="@string/effect_attach_on" /> + </LinearLayout> + + <TextView android:id="@+id/sessionText" + android:layout_width="fill_parent" + android:layout_height="wrap_content" /> + + </LinearLayout> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical" + android:layout_gravity="right" + android:layout_weight="1.0" + android:layout_marginLeft="3dip" + android:layout_marginTop="3dip" + android:layout_marginRight="1dip" + android:layout_marginBottom="3dip" + > + + <TextView android:id="@+id/sendLevelText" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/send_level_name" /> + + <SeekBar android:id="@+id/sendLevelSeekBar" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:max="100" + android:progress="50" + android:layout_marginLeft="10dip" + android:layout_marginRight="30dip" /> + + <TextView android:id="@+id/sendLevelValue" + android:layout_width="fill_parent" + android:layout_height="wrap_content" /> + + </LinearLayout> + </LinearLayout> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <ScrollView + android:layout_width="fill_parent" + android:layout_height="wrap_content" > + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:orientation="vertical" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" + > + + <TextView android:id="@+id/rvbParam1Name" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/rvb_param_1_name" /> + + <SeekBar android:id="@+id/rvbParam1SeekBar" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:max="100" + android:progress="50" + android:layout_marginLeft="10dip" + android:layout_marginRight="30dip" /> + + <TextView android:id="@+id/rvbParam1Value" + android:layout_width="fill_parent" + android:layout_height="wrap_content" /> + + </LinearLayout> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:orientation="vertical" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" + > + + <TextView android:id="@+id/rvbParam2Name" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/rvb_param_2_name" /> + + <SeekBar android:id="@+id/rvbParam2SeekBar" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:max="100" + android:progress="50" + android:layout_marginLeft="10dip" + android:layout_marginRight="30dip" /> + + <TextView android:id="@+id/rvbParam2Value" + android:layout_width="fill_parent" + android:layout_height="wrap_content" /> + + </LinearLayout> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:orientation="vertical" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" + > + + <TextView android:id="@+id/rvbParam3Name" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/rvb_param_3_name" /> + + <SeekBar android:id="@+id/rvbParam3SeekBar" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:max="100" + android:progress="50" + android:layout_marginLeft="10dip" + android:layout_marginRight="30dip" /> + + <TextView android:id="@+id/rvbParam3Value" + android:layout_width="fill_parent" + android:layout_height="wrap_content" /> + + </LinearLayout> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:orientation="vertical" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" + > + + <TextView android:id="@+id/rvbParam4Name" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/rvb_param_4_name" /> + + <SeekBar android:id="@+id/rvbParam4SeekBar" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:max="100" + android:progress="50" + android:layout_marginLeft="10dip" + android:layout_marginRight="30dip" /> + + <TextView android:id="@+id/rvbParam4Value" + android:layout_width="fill_parent" + android:layout_height="wrap_content" /> + + </LinearLayout> + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:orientation="vertical" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" + > + + <TextView android:id="@+id/rvbParam5Name" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/rvb_param_5_name" /> + + <SeekBar android:id="@+id/rvbParam5SeekBar" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:max="100" + android:progress="50" + android:layout_marginLeft="10dip" + android:layout_marginRight="30dip" /> + + <TextView android:id="@+id/rvbParam5Value" + android:layout_width="fill_parent" + android:layout_height="wrap_content" /> + + </LinearLayout> + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:orientation="vertical" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" + > + + <TextView android:id="@+id/rvbParam6Name" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/rvb_param_6_name" /> + + <SeekBar android:id="@+id/rvbParam6SeekBar" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:max="100" + android:progress="50" + android:layout_marginLeft="10dip" + android:layout_marginRight="30dip" /> + + <TextView android:id="@+id/rvbParam6Value" + android:layout_width="fill_parent" + android:layout_height="wrap_content" /> + + </LinearLayout> + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:orientation="vertical" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" + > + + <TextView android:id="@+id/rvbParam7Name" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/rvb_param_7_name" /> + + <SeekBar android:id="@+id/rvbParam7SeekBar" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:max="100" + android:progress="50" + android:layout_marginLeft="10dip" + android:layout_marginRight="30dip" /> + + <TextView android:id="@+id/rvbParam7Value" + android:layout_width="fill_parent" + android:layout_height="wrap_content" /> + + </LinearLayout> + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:orientation="vertical" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" + > + + <TextView android:id="@+id/rvbParam8Name" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/rvb_param_8_name" /> + + <SeekBar android:id="@+id/rvbParam8SeekBar" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:max="100" + android:progress="50" + android:layout_marginLeft="10dip" + android:layout_marginRight="30dip" /> + + <TextView android:id="@+id/rvbParam8Value" + android:layout_width="fill_parent" + android:layout_height="wrap_content" /> + + </LinearLayout> + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:orientation="vertical" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" + > + + <TextView android:id="@+id/rvbParam9Name" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/rvb_param_9_name" /> + + <SeekBar android:id="@+id/rvbParam9SeekBar" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:max="100" + android:progress="50" + android:layout_marginLeft="10dip" + android:layout_marginRight="30dip" /> + + <TextView android:id="@+id/rvbParam9Value" + android:layout_width="fill_parent" + android:layout_height="wrap_content" /> + + </LinearLayout> + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:orientation="vertical" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" + > + + <TextView android:id="@+id/rvbParam10Name" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/rvb_param_10_name" /> + + <SeekBar android:id="@+id/rvbParam10SeekBar" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:max="100" + android:progress="50" + android:layout_marginLeft="10dip" + android:layout_marginRight="30dip" /> + + <TextView android:id="@+id/rvbParam10Value" + android:layout_width="fill_parent" + android:layout_height="wrap_content" /> + + </LinearLayout> + </LinearLayout> + + </ScrollView> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + +</LinearLayout> diff --git a/media/tests/EffectsTest/res/layout/equalizertest.xml b/media/tests/EffectsTest/res/layout/equalizertest.xml new file mode 100755 index 0000000..5ef035d --- /dev/null +++ b/media/tests/EffectsTest/res/layout/equalizertest.xml @@ -0,0 +1,470 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2009 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical"> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <LinearLayout android:id="@+id/eqReleaseLayout" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" > + + <TextView android:id="@+id/eqReleaseText" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1.0" + android:layout_gravity="center_vertical|left" + android:text="@string/effect_release" + style="@android:style/TextAppearance.Medium" /> + + <ToggleButton android:id="@+id/eqReleaseButton" + android:layout_width="wrap_content" + android:layout_height="fill_parent" + android:layout_gravity="center_vertical|right" + android:layout_weight="0.0" /> + + </LinearLayout> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <LinearLayout android:id="@+id/eqControlLayout" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" > + + <TextView android:id="@+id/eqControlText" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1.0" + android:layout_gravity="center_vertical|left" + android:text="@string/effect_control" + style="@android:style/TextAppearance.Medium" /> + + <ToggleButton android:id="@+id/equalizerOnOff" + android:layout_width="wrap_content" + android:layout_height="fill_parent" + android:layout_gravity="center_vertical|right" + android:layout_weight="0.0" /> + + </LinearLayout> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <LinearLayout android:id="@+id/SessionFrame" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" > + + <TextView android:id="@+id/sessionText" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="0.5" + android:layout_gravity="center_vertical|left" + android:text="@string/session" + style="@android:style/TextAppearance.Medium" /> + + <EditText android:id="@+id/sessionEdit" + android:singleLine="true" + android:numeric="integer" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_weight="0.5" + android:layout_gravity="center_vertical|right" /> + </LinearLayout> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <ScrollView + android:layout_width="fill_parent" + android:layout_height="wrap_content" > + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:orientation="vertical" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" + > + + <TextView android:id="@+id/eqParam1Name" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/eq_param_1_name" /> + + <LinearLayout android:id="@+id/eqParam1Desc" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" > + + <TextView android:id="@+id/eqParam1Min" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1.0" /> + <TextView android:id="@+id/eqParam1Center" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1.0" /> + <TextView android:id="@+id/eqParam1Max" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1.0" /> + </LinearLayout> + + <SeekBar android:id="@+id/eqParam1SeekBar" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:max="100" + android:progress="50" + android:layout_marginLeft="10dip" + android:layout_marginRight="30dip" /> + + <TextView android:id="@+id/eqParam1Value" + android:layout_width="fill_parent" + android:layout_height="wrap_content" /> + + </LinearLayout> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:orientation="vertical" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" + > + + <TextView android:id="@+id/eqParam2Name" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/eq_param_2_name" /> + + <LinearLayout android:id="@+id/eqParam2Desc" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" > + + <TextView android:id="@+id/eqParam2Min" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1.0" /> + <TextView android:id="@+id/eqParam2Center" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1.0" /> + <TextView android:id="@+id/eqParam2Max" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1.0" /> + + </LinearLayout> + + <SeekBar android:id="@+id/eqParam2SeekBar" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:max="100" + android:progress="50" + android:layout_marginLeft="10dip" + android:layout_marginRight="30dip" /> + + <TextView android:id="@+id/eqParam2Value" + android:layout_width="fill_parent" + android:layout_height="wrap_content" /> + + </LinearLayout> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:orientation="vertical" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" + > + + <TextView android:id="@+id/eqParam3Name" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/eq_param_3_name" /> + + <LinearLayout android:id="@+id/eqParam3Desc" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" > + + <TextView android:id="@+id/eqParam3Min" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1.0" /> + <TextView android:id="@+id/eqParam3Center" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1.0" /> + <TextView android:id="@+id/eqParam3Max" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1.0" /> + + </LinearLayout> + + <SeekBar android:id="@+id/eqParam3SeekBar" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:max="100" + android:progress="50" + android:layout_marginLeft="10dip" + android:layout_marginRight="30dip" /> + + <TextView android:id="@+id/eqParam3Value" + android:layout_width="fill_parent" + android:layout_height="wrap_content" /> + + </LinearLayout> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:orientation="vertical" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" + > + + <TextView android:id="@+id/eqParam4Name" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/eq_param_4_name" /> + + <LinearLayout android:id="@+id/eqParam4Desc" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" > + + <TextView android:id="@+id/eqParam4Min" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1.0" /> + <TextView android:id="@+id/eqParam4Center" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1.0" /> + <TextView android:id="@+id/eqParam4Max" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1.0" /> + + </LinearLayout> + + <SeekBar android:id="@+id/eqParam4SeekBar" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:max="100" + android:progress="50" + android:layout_marginLeft="10dip" + android:layout_marginRight="30dip" /> + + <TextView android:id="@+id/eqParam4Value" + android:layout_width="fill_parent" + android:layout_height="wrap_content" /> + + </LinearLayout> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:orientation="vertical" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" + > + + <TextView android:id="@+id/eqParam5Name" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/eq_param_5_name" /> + + <LinearLayout android:id="@+id/eqParam5Desc" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" > + + <TextView android:id="@+id/eqParam5Min" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1.0" /> + <TextView android:id="@+id/eqParam5Center" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1.0" /> + <TextView android:id="@+id/eqParam5Max" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1.0" /> + + </LinearLayout> + + <SeekBar android:id="@+id/eqParam5SeekBar" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:max="100" + android:progress="50" + android:layout_marginLeft="10dip" + android:layout_marginRight="30dip" /> + + <TextView android:id="@+id/eqParam5Value" + android:layout_width="fill_parent" + android:layout_height="wrap_content" /> + + </LinearLayout> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:orientation="vertical" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" + > + + <TextView android:id="@+id/eqParam6Name" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/eq_param_6_name" /> + + <SeekBar android:id="@+id/eqParam6SeekBar" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:max="100" + android:progress="50" + android:layout_marginLeft="10dip" + android:layout_marginRight="30dip" /> + + <TextView android:id="@+id/eqParam6Value" + android:layout_width="fill_parent" + android:layout_height="wrap_content" /> + + </LinearLayout> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + </LinearLayout> + + </ScrollView> + +</LinearLayout> diff --git a/media/tests/EffectsTest/res/layout/presetreverbtest.xml b/media/tests/EffectsTest/res/layout/presetreverbtest.xml new file mode 100755 index 0000000..cd7fbd3 --- /dev/null +++ b/media/tests/EffectsTest/res/layout/presetreverbtest.xml @@ -0,0 +1,179 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical"> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <LinearLayout android:id="@+id/presetrvbReleaseLayout" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" > + + <TextView android:id="@+id/presetrvbReleaseText" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1.0" + android:layout_gravity="center_vertical|left" + android:text="@string/effect_release" + style="@android:style/TextAppearance.Medium" /> + + <ToggleButton android:id="@+id/presetrvbReleaseButton" + android:layout_width="wrap_content" + android:layout_height="fill_parent" + android:layout_gravity="center_vertical|right" + android:layout_weight="0.0" /> + + </LinearLayout> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <LinearLayout android:id="@+id/presetrvbControlLayout" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" > + + <TextView android:id="@+id/presetrvbControlText" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1.0" + android:layout_gravity="center_vertical|left" + android:text="@string/effect_control" + style="@android:style/TextAppearance.Medium" /> + + <ToggleButton android:id="@+id/presetrvbOnOff" + android:layout_width="wrap_content" + android:layout_height="fill_parent" + android:layout_gravity="center_vertical|right" + android:layout_weight="0.0" /> + + </LinearLayout> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <LinearLayout android:id="@+id/SessionFrame" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" > + + <TextView android:id="@+id/sessionText" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="0.5" + android:layout_gravity="center_vertical|left" + android:text="@string/session" + style="@android:style/TextAppearance.Medium" /> + + <EditText android:id="@+id/sessionEdit" + android:singleLine="true" + android:numeric="integer" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_weight="0.5" + android:layout_gravity="center_vertical|right" /> + </LinearLayout> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <ScrollView + android:layout_width="fill_parent" + android:layout_height="wrap_content" > + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:orientation="vertical" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" + > + + <TextView android:id="@+id/presetrvbParam1Name" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/presetrvb_param_1_name" /> + + <SeekBar android:id="@+id/presetrvbParam1SeekBar" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:max="100" + android:progress="50" + android:layout_marginLeft="10dip" + android:layout_marginRight="30dip" /> + + <TextView android:id="@+id/presetrvbParam1Value" + android:layout_width="fill_parent" + android:layout_height="wrap_content" /> + + </LinearLayout> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + </LinearLayout> + + </ScrollView> + +</LinearLayout> diff --git a/media/tests/EffectsTest/res/layout/virtualizertest.xml b/media/tests/EffectsTest/res/layout/virtualizertest.xml new file mode 100755 index 0000000..1fafeab --- /dev/null +++ b/media/tests/EffectsTest/res/layout/virtualizertest.xml @@ -0,0 +1,194 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2009 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical"> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <LinearLayout android:id="@+id/virtReleaseLayout" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" > + + <TextView android:id="@+id/virtReleaseText" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1.0" + android:layout_gravity="center_vertical|left" + android:text="@string/effect_release" + style="@android:style/TextAppearance.Medium" /> + + <ToggleButton android:id="@+id/virtReleaseButton" + android:layout_width="wrap_content" + android:layout_height="fill_parent" + android:layout_gravity="center_vertical|right" + android:layout_weight="0.0" /> + + </LinearLayout> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <LinearLayout android:id="@+id/virtControlLayout" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" > + + <TextView android:id="@+id/virtControlText" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1.0" + android:layout_gravity="center_vertical|left" + android:text="@string/effect_control" + style="@android:style/TextAppearance.Medium" /> + + <ToggleButton android:id="@+id/virtualizerOnOff" + android:layout_width="wrap_content" + android:layout_height="fill_parent" + android:layout_gravity="center_vertical|right" + android:layout_weight="0.0" /> + + </LinearLayout> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <LinearLayout android:id="@+id/SessionFrame" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" > + + <TextView android:id="@+id/sessionText" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="0.5" + android:layout_gravity="center_vertical|left" + android:text="@string/session" + style="@android:style/TextAppearance.Medium" /> + + <EditText android:id="@+id/sessionEdit" + android:singleLine="true" + android:numeric="integer" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_weight="0.5" + android:layout_gravity="center_vertical|right" /> + </LinearLayout> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <ScrollView + android:layout_width="fill_parent" + android:layout_height="wrap_content" > + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:orientation="vertical" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" + > + + <TextView android:id="@+id/virtStrengthName" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/stength_name" /> + + <LinearLayout android:id="@+id/virtStrengthDesc" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="30dip" + android:layout_marginBottom="10dip" > + + <TextView android:id="@+id/virtStrengthMin" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1" + android:layout_gravity="left" + android:gravity="left"/> + <TextView android:id="@+id/virtStrengthMax" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1" + android:layout_gravity="right" + android:gravity="right"/> + </LinearLayout> + + <SeekBar android:id="@+id/virtStrengthSeekBar" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:max="100" + android:progress="50" + android:layout_marginLeft="10dip" + android:layout_marginRight="30dip" /> + + <TextView android:id="@+id/virtStrengthValue" + android:layout_width="fill_parent" + android:layout_height="wrap_content" /> + + </LinearLayout> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + </LinearLayout> + + </ScrollView> + +</LinearLayout> diff --git a/media/tests/EffectsTest/res/layout/visualizertest.xml b/media/tests/EffectsTest/res/layout/visualizertest.xml new file mode 100755 index 0000000..50ac7bb --- /dev/null +++ b/media/tests/EffectsTest/res/layout/visualizertest.xml @@ -0,0 +1,263 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2009 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical"> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <LinearLayout android:id="@+id/visuReleaseLayout" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" > + + <TextView android:id="@+id/visuReleaseText" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1.0" + android:layout_gravity="center_vertical|left" + android:text="@string/effect_release" + style="@android:style/TextAppearance.Medium" /> + + <ToggleButton android:id="@+id/visuReleaseButton" + android:layout_width="wrap_content" + android:layout_height="fill_parent" + android:layout_gravity="center_vertical|right" + android:layout_weight="0.0" /> + + </LinearLayout> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <LinearLayout android:id="@+id/visuControlLayout" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" > + + <TextView android:id="@+id/visuControlText" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1.0" + android:layout_gravity="center_vertical|left" + android:text="@string/effect_control" + style="@android:style/TextAppearance.Medium" /> + + <ToggleButton android:id="@+id/visualizerOnOff" + android:layout_width="wrap_content" + android:layout_height="fill_parent" + android:layout_gravity="center_vertical|right" + android:layout_weight="0.0" /> + + </LinearLayout> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <LinearLayout android:id="@+id/SessionFrame" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" > + + <TextView android:id="@+id/sessionText" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="0.5" + android:layout_gravity="center_vertical|left" + android:text="@string/session" + style="@android:style/TextAppearance.Medium" /> + + <EditText android:id="@+id/sessionEdit" + android:singleLine="true" + android:numeric="integer" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_weight="0.5" + android:layout_gravity="center_vertical|right" /> + </LinearLayout> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <LinearLayout android:id="@+id/visuCallbackLayout" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" > + + <TextView android:id="@+id/visuCallbackText" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1.0" + android:layout_gravity="center_vertical|left" + android:text="@string/visu_callback" + style="@android:style/TextAppearance.Medium" /> + + <ToggleButton android:id="@+id/visuCallbackOnOff" + android:layout_width="wrap_content" + android:layout_height="fill_parent" + android:layout_gravity="center_vertical|right" + android:layout_weight="0.0" /> + + </LinearLayout> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <ScrollView + android:layout_width="fill_parent" + android:layout_height="wrap_content" > + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:orientation="vertical" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" + > + + <TextView android:id="@+id/waveformName" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/waveform_name" /> + + <LinearLayout android:id="@+id/eqParam1Desc" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" > + + <TextView android:id="@+id/waveformMin" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1.0" /> + <TextView android:id="@+id/waveformCenter" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1.0" /> + <TextView android:id="@+id/waveformMax" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1.0" /> + + </LinearLayout> + + </LinearLayout> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:orientation="vertical" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" + > + + <TextView android:id="@+id/fftName" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/fft_name" /> + + <LinearLayout android:id="@+id/eqParam1Desc" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" > + + <TextView android:id="@+id/fftMin" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1.0" /> + <TextView android:id="@+id/fftCenter" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1.0" /> + <TextView android:id="@+id/fftMax" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1.0" /> + + </LinearLayout> + + </LinearLayout> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + + + </LinearLayout> + + </ScrollView> + +</LinearLayout> diff --git a/media/tests/EffectsTest/res/raw/mp3_sample.mp3 b/media/tests/EffectsTest/res/raw/mp3_sample.mp3 Binary files differnew file mode 100644 index 0000000..a9d8635 --- /dev/null +++ b/media/tests/EffectsTest/res/raw/mp3_sample.mp3 diff --git a/media/tests/EffectsTest/res/raw/sine440_mo_16b_16k.wav b/media/tests/EffectsTest/res/raw/sine440_mo_16b_16k.wav Binary files differnew file mode 100644 index 0000000..2538b4d6 --- /dev/null +++ b/media/tests/EffectsTest/res/raw/sine440_mo_16b_16k.wav diff --git a/media/tests/EffectsTest/res/values/strings.xml b/media/tests/EffectsTest/res/values/strings.xml new file mode 100755 index 0000000..2a85184 --- /dev/null +++ b/media/tests/EffectsTest/res/values/strings.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="app_name">Effects Test</string> + <string name="effect_control">Effect State</string> + <string name="effect_release">Effect Instantiated</string> + <string name="effect_bypass">Bypass</string> + <string name="envreverb_test_name">Environmental Reverb Test</string> + <string name="rvb_param_1_name">Room Level</string> + <string name="rvb_param_2_name">Room HF Level</string> + <string name="rvb_param_3_name">Decay Time</string> + <string name="rvb_param_4_name">Decay HF Ratio</string> + <string name="rvb_param_5_name">Reflections Level</string> + <string name="rvb_param_6_name">Reflections Delay</string> + <string name="rvb_param_7_name">Reverb Level</string> + <string name="rvb_param_8_name">Reverb Delay</string> + <string name="rvb_param_9_name">Diffusion</string> + <string name="rvb_param_10_name">Density</string> + <string name="presetrvb_test_name">Preset Reverb Test</string> + <string name="presetrvb_param_1_name">Presets</string> + <string name="equalizer_test_name">Equalizer Test</string> + <string name="session">Audio Session</string> + <string name="eq_param_1_name">Band 1 Level</string> + <string name="eq_param_2_name">Band 2 Level</string> + <string name="eq_param_3_name">Band 3 Level</string> + <string name="eq_param_4_name">Band 4 Level</string> + <string name="eq_param_5_name">Band 5 Level</string> + <string name="eq_param_6_name">Presets</string> + <string name="virtualizer_test_name">Virtualizer Test</string> + <string name="stength_name">Strength</string> + <string name="bassboost_test_name">Bass Boost Test</string> + <string name="visualizer_test_name">Visualizer Test</string> + <string name="visu_callback">Callback Mode</string> + <string name="waveform_name">PCM capture</string> + <string name="fft_name">FFT Capture</string> + <string name="effect_attach_off">Attach</string> + <string name="effect_attach_on">Detach</string> + <string name="send_level_name">Send Level</string> +</resources> diff --git a/media/tests/EffectsTest/src/com/android/effectstest/BassBoostTest.java b/media/tests/EffectsTest/src/com/android/effectstest/BassBoostTest.java new file mode 100755 index 0000000..1a10d64 --- /dev/null +++ b/media/tests/EffectsTest/src/com/android/effectstest/BassBoostTest.java @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2009 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 com.android.effectstest; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.View.OnClickListener; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; +import android.widget.EditText; +import android.widget.SeekBar; +import android.widget.ToggleButton; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import java.nio.ByteOrder; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; + +import android.media.audiofx.BassBoost; +import android.media.audiofx.AudioEffect; + +public class BassBoostTest extends Activity implements OnCheckedChangeListener { + + private final static String TAG = "BassBoostTest"; + + private static int NUM_PARAMS = 1; + + private EffectParameter mStrength; + private BassBoost mBassBoost = null; + ToggleButton mOnOffButton; + ToggleButton mReleaseButton; + EditText mSessionText; + static int sSession = 0; + EffectListner mEffectListener = new EffectListner(); + private static HashMap<Integer, BassBoost> sInstances = new HashMap<Integer, BassBoost>(10); + String mSettings = ""; + + public BassBoostTest() { + Log.d(TAG, "contructor"); + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + SeekBar seekBar; + TextView textView; + + setContentView(R.layout.bassboosttest); + + mSessionText = (EditText) findViewById(R.id.sessionEdit); + mSessionText.setOnKeyListener(mSessionKeyListener); + + mSessionText.setText(Integer.toString(sSession)); + + mReleaseButton = (ToggleButton)findViewById(R.id.bbReleaseButton); + mOnOffButton = (ToggleButton)findViewById(R.id.bassboostOnOff); + + getEffect(sSession); + + if (mBassBoost != null) { + mReleaseButton.setOnCheckedChangeListener(this); + mOnOffButton.setOnCheckedChangeListener(this); + + textView = (TextView)findViewById(R.id.bbStrengthMin); + textView.setText("0"); + textView = (TextView)findViewById(R.id.bbStrengthMax); + textView.setText("1000"); + seekBar = (SeekBar)findViewById(R.id.bbStrengthSeekBar); + textView = (TextView)findViewById(R.id.bbStrengthValue); + mStrength = new BassBoostParam(mBassBoost, 0, 1000, seekBar, textView); + seekBar.setOnSeekBarChangeListener(mStrength); + mStrength.setEnabled(mBassBoost.getStrengthSupported()); + } + } + + @Override + public void onResume() { + super.onResume(); + } + + @Override + public void onPause() { + super.onPause(); + } + + private View.OnKeyListener mSessionKeyListener + = new View.OnKeyListener() { + public boolean onKey(View v, int keyCode, KeyEvent event) { + Log.d(TAG, "onKey() keyCode: "+keyCode+" event.getAction(): "+event.getAction()); + if (event.getAction() == KeyEvent.ACTION_DOWN) { + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_CENTER: + case KeyEvent.KEYCODE_ENTER: + try { + sSession = Integer.parseInt(mSessionText.getText().toString()); + getEffect(sSession); + if (mBassBoost != null) { + mStrength.setEffect(mBassBoost); + mStrength.setEnabled(mBassBoost.getStrengthSupported()); + } + } catch (NumberFormatException e) { + Log.d(TAG, "Invalid session #: "+mSessionText.getText().toString()); + } + return true; + } + } + return false; + } + }; + + // OnCheckedChangeListener + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (buttonView.getId() == R.id.bassboostOnOff) { + if (mBassBoost != null) { + mBassBoost.setEnabled(isChecked); + mStrength.updateDisplay(); + } + } + if (buttonView.getId() == R.id.bbReleaseButton) { + if (isChecked) { + if (mBassBoost == null) { + getEffect(sSession); + if (mBassBoost != null) { + mStrength.setEffect(mBassBoost); + mStrength.setEnabled(mBassBoost.getStrengthSupported()); + } + } + } else { + if (mBassBoost != null) { + mStrength.setEnabled(false); + putEffect(sSession); + } + } + } + } + + private class BassBoostParam extends EffectParameter { + private BassBoost mBassBoost; + + public BassBoostParam(BassBoost bassboost, int min, int max, SeekBar seekBar, TextView textView) { + super (min, max, seekBar, textView, "o/oo"); + + mBassBoost = bassboost; + updateDisplay(); + } + + @Override + public void setParameter(Integer value) { + if (mBassBoost != null) { + mBassBoost.setStrength(value.shortValue()); + } + } + + @Override + public Integer getParameter() { + if (mBassBoost != null) { + return new Integer(mBassBoost.getRoundedStrength()); + } + return new Integer(0); + } + + @Override + public void setEffect(Object effect) { + mBassBoost = (BassBoost)effect; + } + } + + public class EffectListner implements AudioEffect.OnEnableStatusChangeListener, + AudioEffect.OnControlStatusChangeListener, AudioEffect.OnParameterChangeListener + { + public EffectListner() { + } + public void onEnableStatusChange(AudioEffect effect, boolean enabled) { + Log.d(TAG,"onEnableStatusChange: "+ enabled); + } + public void onControlStatusChange(AudioEffect effect, boolean controlGranted) { + Log.d(TAG,"onControlStatusChange: "+ controlGranted); + } + public void onParameterChange(AudioEffect effect, int status, byte[] param, byte[] value) { + int p = byteArrayToInt(param, 0); + short v = byteArrayToShort(value, 0); + + Log.d(TAG,"onParameterChange, status: "+status+" p: "+p+" v: "+v); + } + + private int byteArrayToInt(byte[] valueBuf, int offset) { + ByteBuffer converter = ByteBuffer.wrap(valueBuf); + converter.order(ByteOrder.nativeOrder()); + return converter.getInt(offset); + + } + private short byteArrayToShort(byte[] valueBuf, int offset) { + ByteBuffer converter = ByteBuffer.wrap(valueBuf); + converter.order(ByteOrder.nativeOrder()); + return converter.getShort(offset); + + } + + } + + private void getEffect(int session) { + synchronized (sInstances) { + if (sInstances.containsKey(session)) { + mBassBoost = sInstances.get(session); + } else { + try{ + mBassBoost = new BassBoost(0, session); + } catch (IllegalArgumentException e) { + Log.e(TAG,"BassBoost effect not supported"); + } catch (IllegalStateException e) { + Log.e(TAG,"BassBoost cannot get strength supported"); + } catch (UnsupportedOperationException e) { + Log.e(TAG,"BassBoost library not loaded"); + } catch (RuntimeException e) { + Log.e(TAG,"BassBoost effect not found"); + } + sInstances.put(session, mBassBoost); + } + mReleaseButton.setEnabled(false); + mOnOffButton.setEnabled(false); + + if (mBassBoost != null) { + if (mSettings != "") { + mBassBoost.setProperties(new BassBoost.Settings(mSettings)); + } + mBassBoost.setEnableStatusListener(mEffectListener); + mBassBoost.setControlStatusListener(mEffectListener); + mBassBoost.setParameterListener(mEffectListener); + + mReleaseButton.setChecked(true); + mReleaseButton.setEnabled(true); + + mOnOffButton.setChecked(mBassBoost.getEnabled()); + mOnOffButton.setEnabled(true); + } + } + } + + private void putEffect(int session) { + mOnOffButton.setChecked(false); + mOnOffButton.setEnabled(false); + synchronized (sInstances) { + if (mBassBoost != null) { + mSettings = mBassBoost.getProperties().toString(); + mBassBoost.release(); + Log.d(TAG,"BassBoost released"); + mBassBoost = null; + sInstances.remove(session); + } + } + } + +} diff --git a/media/tests/EffectsTest/src/com/android/effectstest/EffectParameter.java b/media/tests/EffectsTest/src/com/android/effectstest/EffectParameter.java new file mode 100755 index 0000000..95077e7 --- /dev/null +++ b/media/tests/EffectsTest/src/com/android/effectstest/EffectParameter.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2009 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 com.android.effectstest; + +import android.os.Bundle; +import android.util.Log; +import android.widget.TextView; +import android.widget.SeekBar; + + +abstract class EffectParameter implements SeekBar.OnSeekBarChangeListener { + + private final static String TAG = "EffectParameter"; + + protected int mMin; + protected int mMax; + protected String mUnit; + protected SeekBar mSeekBar; + protected TextView mValueText; + + public EffectParameter (int min, int max, SeekBar seekBar, TextView textView, String unit) { + mMin = min; + mMax = max; + mSeekBar = seekBar; + mValueText = textView; + mUnit = unit; + byte[] paramBuf = new byte[4]; + + mSeekBar.setMax(max-min); + } + + public void displayValue(int value, boolean fromTouch) { + String text = Integer.toString(value)+" "+mUnit; + mValueText.setText(text); + if (!fromTouch) { + mSeekBar.setProgress(value - mMin); + } + } + + public void updateDisplay() { + displayValue(getParameter(), false); + } + + public abstract void setParameter(Integer value); + + public abstract Integer getParameter(); + + public abstract void setEffect(Object effect); + + // SeekBar.OnSeekBarChangeListener + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch) { + + if (seekBar != mSeekBar) { + Log.e(TAG, "onProgressChanged called with wrong seekBar"); + return; + } + + int value = progress + mMin; + if (fromTouch) { + setParameter(value); + } + + displayValue(getParameter(), fromTouch); + } + + public void onStartTrackingTouch(SeekBar seekBar) { + } + + public void onStopTrackingTouch(SeekBar seekBar) { + } + + public void setEnabled(boolean e) { + mSeekBar.setEnabled(e); + } +} diff --git a/media/tests/EffectsTest/src/com/android/effectstest/EffectsTest.java b/media/tests/EffectsTest/src/com/android/effectstest/EffectsTest.java new file mode 100755 index 0000000..7020246 --- /dev/null +++ b/media/tests/EffectsTest/src/com/android/effectstest/EffectsTest.java @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2009 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 com.android.effectstest; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.View.OnClickListener; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; +import android.widget.ListView; +import android.widget.BaseAdapter; +import android.widget.LinearLayout; +import android.media.audiofx.AudioEffect; + +import java.util.UUID; + +public class EffectsTest extends Activity { + + private final static String TAG = "EffectsTest"; + + + public EffectsTest() { + Log.d(TAG, "contructor"); + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + setContentView(R.layout.effectstest); + + Button button = (Button) findViewById(R.id.env_reverb_actvity); + button.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + startActivity(new Intent(EffectsTest.this, EnvReverbTest.class)); + } + }); + + button = (Button) findViewById(R.id.preset_reverb_actvity); + button.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + startActivity(new Intent(EffectsTest.this, PresetReverbTest.class)); + } + }); + + button = (Button) findViewById(R.id.equalizer_actvity); + button.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + startActivity(new Intent(EffectsTest.this, EqualizerTest.class)); + } + }); + + button = (Button) findViewById(R.id.virtualizer_actvity); + button.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + startActivity(new Intent(EffectsTest.this, VirtualizerTest.class)); + } + }); + + button = (Button) findViewById(R.id.bassboost_actvity); + button.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + startActivity(new Intent(EffectsTest.this, BassBoostTest.class)); + } + }); + + button = (Button) findViewById(R.id.visualizer_actvity); + button.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + startActivity(new Intent(EffectsTest.this, VisualizerTest.class)); + } + }); + + AudioEffect.Descriptor[] descriptors = AudioEffect.queryEffects(); + + ListView list = (ListView) findViewById(R.id.effect_list); + list.setAdapter(new EffectListAdapter(this, descriptors)); + + } + + private class EffectListAdapter extends BaseAdapter { + + private Context mContext; + + AudioEffect.Descriptor[] mDescriptors; + + public EffectListAdapter(Context context, AudioEffect.Descriptor[] descriptors) { + Log.d(TAG, "EffectListAdapter contructor"); + mContext = context; + mDescriptors = descriptors; + for (int i = 0; i < mDescriptors.length; i++) { + Log.d(TAG, "Effect: "+i+" name: "+ mDescriptors[i].name); + } + } + + public int getCount() { + Log.d(TAG, "EffectListAdapter getCount(): "+mDescriptors.length); + return mDescriptors.length; + } + + public Object getItem(int position) { + Log.d(TAG, "EffectListAdapter getItem() at: "+position+" name: " + +mDescriptors[position].name); + return mDescriptors[position]; + } + + public long getItemId(int position) { + return position; + } + + public View getView(int position, View convertView, ViewGroup parent) { + EffectView ev; + if (convertView == null) { + Log.d(TAG, "getView() new EffectView position: " + position); + ev = new EffectView(mContext, mDescriptors); + } else { + Log.d(TAG, "getView() convertView position: " + position); + ev = new EffectView(mContext, mDescriptors); + //ev = (EffectView) convertView; + } + ev.set(position); + return ev; + } + } + + private class EffectView extends LinearLayout { + private Context mContext; + AudioEffect.Descriptor[] mDescriptors; + + public EffectView(Context context, AudioEffect.Descriptor[] descriptors) { + super(context); + + mContext = context; + mDescriptors = descriptors; + this.setOrientation(VERTICAL); + } + + public String effectUuidToString(UUID effectType) { + if (effectType.equals(AudioEffect.EFFECT_TYPE_VIRTUALIZER)) { + return "Virtualizer"; + } else if (effectType.equals(AudioEffect.EFFECT_TYPE_ENV_REVERB)){ + return "Reverb"; + } else if (effectType.equals(AudioEffect.EFFECT_TYPE_PRESET_REVERB)){ + return "Preset Reverb"; + } else if (effectType.equals(AudioEffect.EFFECT_TYPE_EQUALIZER)){ + return "Equalizer"; + } else if (effectType.equals(AudioEffect.EFFECT_TYPE_BASS_BOOST)){ + return "Bass Boost"; + } else if (effectType.equals(AudioEffect.EFFECT_TYPE_AGC)){ + return "Automatic Gain Control"; + } else if (effectType.equals(AudioEffect.EFFECT_TYPE_AEC)){ + return "Acoustic Echo Canceler"; + } else if (effectType.equals(AudioEffect.EFFECT_TYPE_NS)){ + return "Noise Suppressor"; + } + + return effectType.toString(); + } + + public void set(int position) { + TextView tv = new TextView(mContext); + tv.setText("Effect "+ position); + addView(tv, new LinearLayout.LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); + tv = new TextView(mContext); + tv.setText(" type: "+ effectUuidToString(mDescriptors[position].type)); + addView(tv, new LinearLayout.LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); + tv = new TextView(mContext); + tv.setText(" uuid: "+ mDescriptors[position].uuid.toString()); + addView(tv, new LinearLayout.LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); + tv = new TextView(mContext); + tv.setText(" name: "+ mDescriptors[position].name); + addView(tv, new LinearLayout.LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); + tv = new TextView(mContext); + tv.setText(" vendor: "+ mDescriptors[position].implementor); + addView(tv, new LinearLayout.LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); + tv = new TextView(mContext); + tv.setText(" mode: "+ mDescriptors[position].connectMode); + addView(tv, new LinearLayout.LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); + } + } + +} diff --git a/media/tests/EffectsTest/src/com/android/effectstest/EnvReverbTest.java b/media/tests/EffectsTest/src/com/android/effectstest/EnvReverbTest.java new file mode 100755 index 0000000..594e844 --- /dev/null +++ b/media/tests/EffectsTest/src/com/android/effectstest/EnvReverbTest.java @@ -0,0 +1,568 @@ +/* + * Copyright (C) 2009 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 com.android.effectstest; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.View.OnClickListener; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; +import android.widget.SeekBar; +import android.widget.ToggleButton; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.ImageButton; +import android.widget.ImageView; +import java.util.HashMap; +import java.util.Map; + +import android.media.audiofx.EnvironmentalReverb; +import android.media.audiofx.AudioEffect; +import android.media.AudioManager; + +public class EnvReverbTest extends Activity implements OnCheckedChangeListener, SeekBar.OnSeekBarChangeListener { + + private final static String TAG = "EnvReverbTest"; + + private static int NUM_PARAMS = 10; + + private EffectParameter[] mParameters = new EffectParameter[NUM_PARAMS]; + private EnvironmentalReverb mReverb; + ToggleButton mOnOffButton; + ToggleButton mReleaseButton; + ToggleButton mAttachButton; + private static HashMap<Integer, EnvironmentalReverb> sInstances = new HashMap<Integer, EnvironmentalReverb>(10); + static SimplePlayer sPlayerController = null; + SeekBar mSendLevelSeekBar; + TextView mSendLevelDisplay; + static float sSendLevel = linToExp(50,100); + static boolean sAttached = false; + String mSettings = ""; + + public EnvReverbTest() { + Log.d(TAG, "contructor"); + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + Log.d(TAG, "onCreate"); + SeekBar seekBar; + TextView textView; + ToggleButton button; + setContentView(R.layout.envreverbtest); + + ImageView playPause = (ImageView) findViewById(R.id.playPause1); + ImageView stop = (ImageView) findViewById(R.id.stop1); + textView = (TextView) findViewById(R.id.sessionText); + if (sPlayerController == null) { + sPlayerController = new SimplePlayer(this, R.id.playPause1, playPause, + R.id.stop1, stop, textView, + R.raw.mp3_sample, AudioManager.STREAM_MUSIC, 0); + } else { + sPlayerController.set(this, R.id.playPause1, playPause, + R.id.stop1, stop, textView, + AudioManager.STREAM_MUSIC, 0); + } + + // send level + mSendLevelSeekBar = (SeekBar)findViewById(R.id.sendLevelSeekBar); + mSendLevelDisplay = (TextView)findViewById(R.id.sendLevelValue); + mSendLevelSeekBar.setMax(100); + mSendLevelSeekBar.setOnSeekBarChangeListener(this); + mSendLevelSeekBar.setProgress(expToLin(sSendLevel,100)); + sPlayerController.setAuxEffectSendLevel(sSendLevel); + + mOnOffButton = (ToggleButton)findViewById(R.id.rvbOnOff); + mReleaseButton = (ToggleButton)findViewById(R.id.rvbReleaseButton); + mAttachButton = (ToggleButton)findViewById(R.id.attachButton); + + getEffect(0); + + if (mReverb != null) { + mOnOffButton.setOnCheckedChangeListener(this); + mReleaseButton.setOnCheckedChangeListener(this); + mAttachButton.setOnCheckedChangeListener(this); + +// button = (ToggleButton)findViewById(R.id.rvbBypass); +// button.setChecked(false); +// button.setOnCheckedChangeListener(this); + + // Room level + seekBar = (SeekBar)findViewById(R.id.rvbParam1SeekBar); + textView = (TextView)findViewById(R.id.rvbParam1Value); + mParameters[0] = new RoomLevelParam(mReverb, seekBar, textView); + seekBar.setOnSeekBarChangeListener(mParameters[0]); + + // Room HF level + seekBar = (SeekBar)findViewById(R.id.rvbParam2SeekBar); + textView = (TextView)findViewById(R.id.rvbParam2Value); + mParameters[1] = new RoomHFLevelParam(mReverb, seekBar, textView); + seekBar.setOnSeekBarChangeListener(mParameters[1]); + + // Decay time + seekBar = (SeekBar)findViewById(R.id.rvbParam3SeekBar); + textView = (TextView)findViewById(R.id.rvbParam3Value); + mParameters[2] = new DecayTimeParam(mReverb, seekBar, textView); + seekBar.setOnSeekBarChangeListener(mParameters[2]); + + // Decay HF ratio + seekBar = (SeekBar)findViewById(R.id.rvbParam4SeekBar); + textView = (TextView)findViewById(R.id.rvbParam4Value); + mParameters[3] = new DecayHFRatioParam(mReverb, seekBar, textView); + seekBar.setOnSeekBarChangeListener(mParameters[3]); + + // Reflections level + seekBar = (SeekBar)findViewById(R.id.rvbParam5SeekBar); + textView = (TextView)findViewById(R.id.rvbParam5Value); + mParameters[4] = new ReflectionsLevelParam(mReverb, seekBar, textView); + seekBar.setOnSeekBarChangeListener(mParameters[4]); + + // Reflections delay + seekBar = (SeekBar)findViewById(R.id.rvbParam6SeekBar); + textView = (TextView)findViewById(R.id.rvbParam6Value); + mParameters[5] = new ReflectionsDelayParam(mReverb, seekBar, textView); + seekBar.setOnSeekBarChangeListener(mParameters[5]); + + // Reverb level + seekBar = (SeekBar)findViewById(R.id.rvbParam7SeekBar); + textView = (TextView)findViewById(R.id.rvbParam7Value); + mParameters[6] = new ReverbLevelParam(mReverb, seekBar, textView); + seekBar.setOnSeekBarChangeListener(mParameters[6]); + + // Reverb delay + seekBar = (SeekBar)findViewById(R.id.rvbParam8SeekBar); + textView = (TextView)findViewById(R.id.rvbParam8Value); + mParameters[7] = new ReverbDelayParam(mReverb, seekBar, textView); + seekBar.setOnSeekBarChangeListener(mParameters[7]); + + // Diffusion + seekBar = (SeekBar)findViewById(R.id.rvbParam9SeekBar); + textView = (TextView)findViewById(R.id.rvbParam9Value); + mParameters[8] = new DiffusionParam(mReverb, seekBar, textView); + seekBar.setOnSeekBarChangeListener(mParameters[8]); + + // Density + seekBar = (SeekBar)findViewById(R.id.rvbParam10SeekBar); + textView = (TextView)findViewById(R.id.rvbParam10Value); + mParameters[9] = new DensityParam(mReverb, seekBar, textView); + seekBar.setOnSeekBarChangeListener(mParameters[9]); + } + } + @Override + public void onResume() { + super.onResume(); + Log.d(TAG, "onResume"); + } + + @Override + public void onPause() { + super.onPause(); + } + + // OnCheckedChangeListener + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (buttonView.getId() == R.id.rvbOnOff) { + if (mReverb != null) { + mReverb.setEnabled(isChecked); + Log.d(TAG,"onCheckedChanged: rvbOnOff"); + for (int i = 0 ; i < mParameters.length; i++) { + mParameters[i].updateDisplay(); + } + } + } + if (buttonView.getId() == R.id.rvbReleaseButton) { + if (isChecked) { + if (mReverb == null) { + getEffect(0); + for (int i = 0 ; i < mParameters.length; i++) { + mParameters[i].setEffect(mReverb); + mParameters[i].setEnabled(true); + } + } + } else { + if (mReverb != null) { + for (int i = 0 ; i < mParameters.length; i++) { + mParameters[i].setEnabled(false); + } + putEffect(0); + } + } + } +// if (buttonView.getId() == R.id.rvbBypass) { +// // REVERB_PARAM_BYPASS parametervalue is 11 in EffectEnvironmentalReverApi.h +// if (mReverb != null) { +// if (isChecked) { +// mReverb.setParameter((int)11, (int)1); +// } else { +// mReverb.setParameter((int)11, (int)0); +// } +// } +// } + if (buttonView.getId() == R.id.attachButton) { + if (mReverb != null) { + if (isChecked) { + sPlayerController.attachAuxEffect(mReverb.getId()); + sAttached = true; + } else { + sPlayerController.attachAuxEffect(0); + sAttached = false; + } + } + } + } + + // SeekBar.OnSeekBarChangeListener + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch) { + + if (seekBar != mSendLevelSeekBar) { + Log.e(TAG, "onProgressChanged called with wrong seekBar"); + return; + } + + sSendLevel = linToExp(progress,100); + if (fromTouch) { + sPlayerController.setAuxEffectSendLevel(sSendLevel); + } + String text = Float.toString(sSendLevel); + mSendLevelDisplay.setText(text); + if (!fromTouch) { + seekBar.setProgress(progress); + } + } + + static float linToExp(int lin, int range) { + if (lin == 0) return 0; + return (float)Math.pow((double)10,(double)72*(lin-range)/(20*range)); + } + + static int expToLin(float exp, int range) { + if (exp == 0) return 0; + return (int)(20*range*Math.log10((double)exp)/72 + range); + } + + public void onStartTrackingTouch(SeekBar seekBar) { + } + + public void onStopTrackingTouch(SeekBar seekBar) { + } + + private class EnvReverbParam extends EffectParameter { + private EnvironmentalReverb mReverb; + + public EnvReverbParam(EnvironmentalReverb reverb, int min, int max, SeekBar seekBar, TextView textView, String unit) { + super (min, max, seekBar, textView, unit); + mReverb = reverb; + updateDisplay(); + } + + @Override + public void setParameter(Integer value) { + } + + @Override + public Integer getParameter() { + return new Integer(0); + } + + @Override + public void setEffect(Object reverb) { + mReverb = (EnvironmentalReverb)reverb; + } + } + + private class RoomLevelParam extends EnvReverbParam { + + public RoomLevelParam(EnvironmentalReverb reverb, SeekBar seekBar, TextView textView) { + super (reverb, -9600, 0, seekBar, textView, "mB"); + } + + @Override + public void setParameter(Integer value) { + if (mReverb != null) { + mReverb.setRoomLevel(value.shortValue()); + } + } + + @Override + public Integer getParameter() { + if (mReverb != null) { + return new Integer(mReverb.getRoomLevel()); + } + return new Integer(0); + } + } + + private class RoomHFLevelParam extends EnvReverbParam { + + public RoomHFLevelParam(EnvironmentalReverb reverb, SeekBar seekBar, TextView textView) { + super (reverb, -4000, 0, seekBar, textView, "mB"); + } + + @Override + public void setParameter(Integer value) { + if (mReverb != null) { + mReverb.setRoomHFLevel(value.shortValue()); + } + } + + @Override + public Integer getParameter() { + if (mReverb != null) { + return new Integer(mReverb.getRoomHFLevel()); + } + return new Integer(0); + } + } + + private class DecayTimeParam extends EnvReverbParam { + + public DecayTimeParam(EnvironmentalReverb reverb, SeekBar seekBar, TextView textView) { + super (reverb, 200, 4000, seekBar, textView, "ms"); + } + + @Override + public void setParameter(Integer value) { + if (mReverb != null) { + mReverb.setDecayTime(value.intValue()); + } + } + + @Override + public Integer getParameter() { + if (mReverb != null) { + return mReverb.getDecayTime(); + } + return new Integer(0); + } + } + + private class DecayHFRatioParam extends EnvReverbParam { + + public DecayHFRatioParam(EnvironmentalReverb reverb, SeekBar seekBar, TextView textView) { + super (reverb, 100, 1000, seekBar, textView, "permilles"); + } + + @Override + public void setParameter(Integer value) { + if (mReverb != null) { + mReverb.setDecayHFRatio(value.shortValue()); + } + } + + @Override + public Integer getParameter() { + if (mReverb != null) { + return new Integer(mReverb.getDecayHFRatio()); + } + return new Integer(0); + } + } + + private class ReflectionsLevelParam extends EnvReverbParam { + + public ReflectionsLevelParam(EnvironmentalReverb reverb, SeekBar seekBar, TextView textView) { + super (reverb, -9600, 0, seekBar, textView, "mB"); + } + + @Override + public void setParameter(Integer value) { + if (mReverb != null) { + mReverb.setReflectionsLevel(value.shortValue()); + } + } + + @Override + public Integer getParameter() { + if (mReverb != null) { + return new Integer(mReverb.getReflectionsLevel()); + } + return new Integer(0); + } + } + + private class ReflectionsDelayParam extends EnvReverbParam { + + public ReflectionsDelayParam(EnvironmentalReverb reverb, SeekBar seekBar, TextView textView) { + super (reverb, 0, 65, seekBar, textView, "ms"); + } + + @Override + public void setParameter(Integer value) { + if (mReverb != null) { + mReverb.setReflectionsDelay(value.intValue()); + } + } + + @Override + public Integer getParameter() { + if (mReverb != null) { + return mReverb.getReflectionsDelay(); + } + return new Integer(0); + } + } + + private class ReverbLevelParam extends EnvReverbParam { + + public ReverbLevelParam(EnvironmentalReverb reverb, SeekBar seekBar, TextView textView) { + super (reverb, -9600, 2000, seekBar, textView, "mB"); + } + + @Override + public void setParameter(Integer value) { + if (mReverb != null) { + mReverb.setReverbLevel(value.shortValue()); + } + } + + @Override + public Integer getParameter() { + if (mReverb != null) { + return new Integer(mReverb.getReverbLevel()); + } + return new Integer(0); + } + } + + private class ReverbDelayParam extends EnvReverbParam { + + public ReverbDelayParam(EnvironmentalReverb reverb, SeekBar seekBar, TextView textView) { + super (reverb, 0, 65, seekBar, textView, "ms"); + } + + @Override + public void setParameter(Integer value) { + if (mReverb != null) { + mReverb.setReverbDelay(value.intValue()); + } + } + + @Override + public Integer getParameter() { + if (mReverb != null) { + return mReverb.getReverbDelay(); + } + return new Integer(0); + } + } + + private class DiffusionParam extends EnvReverbParam { + + public DiffusionParam(EnvironmentalReverb reverb, SeekBar seekBar, TextView textView) { + super (reverb, 0, 1000, seekBar, textView, "permilles"); + } + + @Override + public void setParameter(Integer value) { + if (mReverb != null) { + mReverb.setDiffusion(value.shortValue()); + } + } + + @Override + public Integer getParameter() { + if (mReverb != null) { + return new Integer(mReverb.getDiffusion()); + } + return new Integer(0); + } + } + + private class DensityParam extends EnvReverbParam { + + public DensityParam(EnvironmentalReverb reverb, SeekBar seekBar, TextView textView) { + super (reverb, 0, 1000, seekBar, textView, "permilles"); + } + + @Override + public void setParameter(Integer value) { + if (mReverb != null) { + mReverb.setDensity(value.shortValue()); + } + } + + @Override + public Integer getParameter() { + if (mReverb != null) { + return new Integer(mReverb.getDensity()); + } + return new Integer(0); + } + } + + private void getEffect(int session) { + synchronized (sInstances) { + if (sInstances.containsKey(session)) { + mReverb = sInstances.get(session); + } else { + try{ + mReverb = new EnvironmentalReverb(0, session); + } catch (IllegalArgumentException e) { + Log.e(TAG,"Reverb effect not supported"); + } catch (UnsupportedOperationException e) { + Log.e(TAG,"Reverb library not loaded"); + } catch (RuntimeException e) { + Log.e(TAG,"Reverb effect not found"); + } + Log.d(TAG, "new reverb: "+mReverb); + sInstances.put(session, mReverb); + } + } + mReleaseButton.setEnabled(false); + mOnOffButton.setEnabled(false); + mAttachButton.setEnabled(false); + if (mReverb != null) { + if (mSettings != "") { + mReverb.setProperties(new EnvironmentalReverb.Settings(mSettings)); + } + mReleaseButton.setChecked(true); + mReleaseButton.setEnabled(true); + mOnOffButton.setChecked(mReverb.getEnabled()); + mOnOffButton.setEnabled(true); + mAttachButton.setChecked(false); + mAttachButton.setEnabled(true); + if (sAttached) { + mAttachButton.setChecked(true); + sPlayerController.attachAuxEffect(mReverb.getId()); + } + } + } + + private void putEffect(int session) { + mOnOffButton.setChecked(false); + mOnOffButton.setEnabled(false); + mAttachButton.setChecked(false); + mAttachButton.setEnabled(false); + synchronized (sInstances) { + if (mReverb != null) { + mSettings = mReverb.getProperties().toString(); + mReverb.release(); + Log.d(TAG,"Reverb released, settings: "+mSettings); + mReverb = null; + sInstances.remove(session); + } + } + } +} diff --git a/media/tests/EffectsTest/src/com/android/effectstest/EqualizerTest.java b/media/tests/EffectsTest/src/com/android/effectstest/EqualizerTest.java new file mode 100755 index 0000000..f30a26f --- /dev/null +++ b/media/tests/EffectsTest/src/com/android/effectstest/EqualizerTest.java @@ -0,0 +1,407 @@ +/* + * Copyright (C) 2009 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 com.android.effectstest; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.View.OnClickListener; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; +import android.widget.EditText; +import android.widget.SeekBar; +import android.widget.ToggleButton; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import java.nio.ByteOrder; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; + + +import android.media.audiofx.Equalizer; +import android.media.audiofx.AudioEffect; + +public class EqualizerTest extends Activity implements OnCheckedChangeListener { + + private final static String TAG = "EqualizerTest"; + + private static int NUM_BANDS = 5; + private static int NUM_PARAMS = NUM_BANDS + 1; + + private EffectParameter[] mParameters = new EffectParameter[NUM_PARAMS]; + private Equalizer mEqualizer; + ToggleButton mOnOffButton; + ToggleButton mReleaseButton; + EditText mSessionText; + static int sSession = 0; + EffectListner mEffectListener = new EffectListner(); + private static HashMap<Integer, Equalizer> sInstances = new HashMap<Integer, Equalizer>(10); + String mSettings = ""; + + public EqualizerTest() { + Log.d(TAG, "contructor"); + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + SeekBar seekBar; + TextView textView; + + setContentView(R.layout.equalizertest); + + mSessionText = (EditText) findViewById(R.id.sessionEdit); + mSessionText.setOnKeyListener(mSessionKeyListener); + + mSessionText.setText(Integer.toString(sSession)); + + mReleaseButton = (ToggleButton)findViewById(R.id.eqReleaseButton); + mOnOffButton = (ToggleButton)findViewById(R.id.equalizerOnOff); + + getEffect(sSession); + + if (mEqualizer != null) { + mReleaseButton.setOnCheckedChangeListener(this); + mOnOffButton.setOnCheckedChangeListener(this); + + short[] bandLevelRange = mEqualizer.getBandLevelRange(); + int centerFreq; + int []freqRange; + + // Band 1 level + centerFreq = mEqualizer.getCenterFreq((short)0); + freqRange = mEqualizer.getBandFreqRange((short)0); + displayFreq(R.id.eqParam1Center, centerFreq); + displayFreq(R.id.eqParam1Min, freqRange[0]); + displayFreq(R.id.eqParam1Max, freqRange[1]); + seekBar = (SeekBar)findViewById(R.id.eqParam1SeekBar); + textView = (TextView)findViewById(R.id.eqParam1Value); + mParameters[0] = new BandLevelParam(mEqualizer, 0, bandLevelRange[0], bandLevelRange[1], seekBar, textView); + seekBar.setOnSeekBarChangeListener(mParameters[0]); + + // Band 2 level + centerFreq = mEqualizer.getCenterFreq((short)1); + freqRange = mEqualizer.getBandFreqRange((short)1); + displayFreq(R.id.eqParam2Center, centerFreq); + displayFreq(R.id.eqParam2Min, freqRange[0]); + displayFreq(R.id.eqParam2Max, freqRange[1]); + seekBar = (SeekBar)findViewById(R.id.eqParam2SeekBar); + textView = (TextView)findViewById(R.id.eqParam2Value); + mParameters[1] = new BandLevelParam(mEqualizer, 1, bandLevelRange[0], bandLevelRange[1], seekBar, textView); + seekBar.setOnSeekBarChangeListener(mParameters[1]); + + // Band 3 level + centerFreq = mEqualizer.getCenterFreq((short)2); + freqRange = mEqualizer.getBandFreqRange((short)2); + displayFreq(R.id.eqParam3Center, centerFreq); + displayFreq(R.id.eqParam3Min, freqRange[0]); + displayFreq(R.id.eqParam3Max, freqRange[1]); + seekBar = (SeekBar)findViewById(R.id.eqParam3SeekBar); + textView = (TextView)findViewById(R.id.eqParam3Value); + mParameters[2] = new BandLevelParam(mEqualizer, 2, bandLevelRange[0], bandLevelRange[1], seekBar, textView); + seekBar.setOnSeekBarChangeListener(mParameters[2]); + + // Band 4 level + centerFreq = mEqualizer.getCenterFreq((short)3); + freqRange = mEqualizer.getBandFreqRange((short)3); + displayFreq(R.id.eqParam4Center, centerFreq); + displayFreq(R.id.eqParam4Min, freqRange[0]); + displayFreq(R.id.eqParam4Max, freqRange[1]); + seekBar = (SeekBar)findViewById(R.id.eqParam4SeekBar); + textView = (TextView)findViewById(R.id.eqParam4Value); + mParameters[3] = new BandLevelParam(mEqualizer, 3, bandLevelRange[0], bandLevelRange[1], seekBar, textView); + seekBar.setOnSeekBarChangeListener(mParameters[3]); + + // Band 5 level + centerFreq = mEqualizer.getCenterFreq((short)4); + freqRange = mEqualizer.getBandFreqRange((short)4); + displayFreq(R.id.eqParam5Center, centerFreq); + displayFreq(R.id.eqParam5Min, freqRange[0]); + displayFreq(R.id.eqParam5Max, freqRange[1]); + seekBar = (SeekBar)findViewById(R.id.eqParam5SeekBar); + textView = (TextView)findViewById(R.id.eqParam5Value); + mParameters[4] = new BandLevelParam(mEqualizer, 4, bandLevelRange[0], bandLevelRange[1], seekBar, textView); + seekBar.setOnSeekBarChangeListener(mParameters[4]); + + // Presets + short numPresets = mEqualizer.getNumberOfPresets(); + seekBar = (SeekBar)findViewById(R.id.eqParam6SeekBar); + textView = (TextView)findViewById(R.id.eqParam6Value); + mParameters[5] = new PresetParam(mEqualizer, (short)0, (short)(numPresets-1), seekBar, textView); + seekBar.setOnSeekBarChangeListener(mParameters[5]); + } + } + + @Override + public void onResume() { + super.onResume(); + } + + @Override + public void onPause() { + super.onPause(); + } + + private View.OnKeyListener mSessionKeyListener + = new View.OnKeyListener() { + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_CENTER: + case KeyEvent.KEYCODE_ENTER: + try { + sSession = Integer.parseInt(mSessionText.getText().toString()); + getEffect(sSession); + if (mEqualizer != null) { + for (int i = 0 ; i < mParameters.length; i++) { + mParameters[i].setEffect(mEqualizer); + mParameters[i].setEnabled(true); + } + } + } catch (NumberFormatException e) { + Log.d(TAG, "Invalid session #: "+mSessionText.getText().toString()); + } + + return true; + } + } + return false; + } + }; + + // OnCheckedChangeListener + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (buttonView.getId() == R.id.equalizerOnOff) { + if (mEqualizer != null) { + mEqualizer.setEnabled(isChecked); + updateBands(); + } + } + if (buttonView.getId() == R.id.eqReleaseButton) { + if (isChecked) { + if (mEqualizer == null) { + getEffect(sSession); + if (mEqualizer != null) { + for (int i = 0 ; i < mParameters.length; i++) { + mParameters[i].setEffect(mEqualizer); + mParameters[i].setEnabled(true); + } + } + } + } else { + if (mEqualizer != null) { + for (int i = 0 ; i < mParameters.length; i++) { + mParameters[i].setEnabled(false); + } + putEffect(sSession); + } + } + } + } + + protected void updateBands() { + for (int i = 0 ; i < NUM_BANDS; i++) { + mParameters[i].updateDisplay(); + } + } + + private void displayFreq(int viewId, int freq) { + TextView textView = (TextView)findViewById(viewId); + String text = Integer.toString(freq/1000)+" Hz"; + textView.setText(text); + } + + private class EqualizerParam extends EffectParameter { + private Equalizer mEqualizer; + + public EqualizerParam(Equalizer equalizer, int min, int max, SeekBar seekBar, TextView textView, String unit) { + super (min, max, seekBar, textView, unit); + + mEqualizer = equalizer; + updateDisplay(); + } + + @Override + public void setParameter(Integer value) { + } + + @Override + public Integer getParameter() { + return new Integer(0); + } + + @Override + public void setEffect(Object eq) { + mEqualizer = (Equalizer)eq; + } + } + + private class BandLevelParam extends EqualizerParam { + private int mBand; + + public BandLevelParam(Equalizer equalizer, int band, short min, short max, SeekBar seekBar, TextView textView) { + super (equalizer, min, max, seekBar, textView, "mB"); + + mBand = band; + mEqualizer = equalizer; + updateDisplay(); + } + + @Override + public void setParameter(Integer value) { + if (mEqualizer != null) { + mEqualizer.setBandLevel((short)mBand, value.shortValue()); + } + } + + @Override + public Integer getParameter() { + if (mEqualizer != null) { + return new Integer(mEqualizer.getBandLevel((short)mBand)); + } + return new Integer(0); + } + } + + private class PresetParam extends EqualizerParam { + + public PresetParam(Equalizer equalizer, short min, short max, SeekBar seekBar, TextView textView) { + super (equalizer, min, max, seekBar, textView, ""); + + mEqualizer = equalizer; + updateDisplay(); + } + + @Override + public void setParameter(Integer value) { + if (mEqualizer != null) { + mEqualizer.usePreset(value.shortValue()); + } + } + + @Override + public Integer getParameter() { + if (mEqualizer != null) { + return new Integer(mEqualizer.getCurrentPreset()); + } + return new Integer(0); + } + + @Override + public void displayValue(int value, boolean fromTouch) { + String text = mEqualizer.getPresetName((short)value); + mValueText.setText(text); + if (!fromTouch) { + mSeekBar.setProgress(value - mMin); + } else { + updateBands(); + } + } + } + + public class EffectListner implements AudioEffect.OnEnableStatusChangeListener, + AudioEffect.OnControlStatusChangeListener, + Equalizer.OnParameterChangeListener + { + public EffectListner() { + } + public void onEnableStatusChange(AudioEffect effect, boolean enabled) { + Log.d(TAG,"onEnableStatusChange: "+ enabled); + } + public void onControlStatusChange(AudioEffect effect, boolean controlGranted) { + Log.d(TAG,"onControlStatusChange: "+ controlGranted); + } + + public void onParameterChange(Equalizer effect, int status, int param1, int param2, int value) { + Log.d(TAG,"onParameterChange EQ, status: "+status+" p1: "+param1+" p2: "+param2+" v: "+value); + } + + private int byteArrayToInt(byte[] valueBuf, int offset) { + ByteBuffer converter = ByteBuffer.wrap(valueBuf); + converter.order(ByteOrder.nativeOrder()); + return converter.getInt(offset); + + } + private short byteArrayToShort(byte[] valueBuf, int offset) { + ByteBuffer converter = ByteBuffer.wrap(valueBuf); + converter.order(ByteOrder.nativeOrder()); + return converter.getShort(offset); + + } + } + + private void getEffect(int session) { + synchronized (sInstances) { + if (sInstances.containsKey(session)) { + mEqualizer = sInstances.get(session); + } else { + try{ + mEqualizer = new Equalizer(0, session); + } catch (IllegalArgumentException e) { + Log.e(TAG,"Equalizer effect not supported"); + } catch (UnsupportedOperationException e) { + Log.e(TAG,"Equalizer library not loaded"); + } catch (IllegalStateException e) { + Log.e(TAG,"Equalizer cannot get presets"); + } catch (RuntimeException e) { + Log.e(TAG,"Equalizer effect not found"); + } + sInstances.put(session, mEqualizer); + } + } + mReleaseButton.setEnabled(false); + mOnOffButton.setEnabled(false); + if (mEqualizer != null) { + if (mSettings != "") { + Log.d(TAG,"Equalizer settings: "+mSettings); + mEqualizer.setProperties(new Equalizer.Settings(mSettings)); + } + + mEqualizer.setEnableStatusListener(mEffectListener); + mEqualizer.setControlStatusListener(mEffectListener); + mEqualizer.setParameterListener(mEffectListener); + + mReleaseButton.setChecked(true); + mReleaseButton.setEnabled(true); + + mOnOffButton.setChecked(mEqualizer.getEnabled()); + mOnOffButton.setEnabled(true); + } + } + + private void putEffect(int session) { +// mOnOffButton.setChecked(false); + mOnOffButton.setEnabled(false); + synchronized (sInstances) { + if (mEqualizer != null) { + mSettings = mEqualizer.getProperties().toString(); + mEqualizer.release(); + Log.d(TAG,"Equalizer released, settings: "+mSettings); + mEqualizer = null; + sInstances.remove(session); + } + } + } +} diff --git a/media/tests/EffectsTest/src/com/android/effectstest/PresetReverbTest.java b/media/tests/EffectsTest/src/com/android/effectstest/PresetReverbTest.java new file mode 100755 index 0000000..91d7948 --- /dev/null +++ b/media/tests/EffectsTest/src/com/android/effectstest/PresetReverbTest.java @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2009 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 com.android.effectstest; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.View.OnClickListener; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; +import android.widget.EditText; +import android.widget.SeekBar; +import android.widget.ToggleButton; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import java.nio.ByteOrder; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; + +import android.media.audiofx.PresetReverb; +import android.media.audiofx.AudioEffect; + +public class PresetReverbTest extends Activity implements OnCheckedChangeListener { + + private final static String TAG = "PresetReverbTest"; + + private static int NUM_PARAMS = 1; + + private EffectParameter[] mParameters = new EffectParameter[NUM_PARAMS]; + private PresetReverb mPresetReverb; + ToggleButton mOnOffButton; + ToggleButton mReleaseButton; + EditText mSessionText; + static int sSession = 0; + EffectListner mEffectListener = new EffectListner(); + private static HashMap<Integer, PresetReverb> sInstances = new HashMap<Integer, PresetReverb>(10); + String mSettings = ""; + + public PresetReverbTest() { + Log.d(TAG, "contructor"); + } + + private static String[] sPresetNames = { + "NONE", //PresetReverb.PRESET_NONE + "SMALLROOM", //PresetReverb.PRESET_SMALLROOM + "MEDIUMROOM", //PresetReverb.PRESET_MEDIUMROOM + "LARGEROOM", //PresetReverb.PRESET_LARGEROOM + "MEDIUMHALL", //PresetReverb.PRESET_MEDIUMHALL + "LARGEHALL", //PresetReverb.PRESET_LARGEHALL + "PLATE", //PresetReverb.PRESET_PLATE + }; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + setContentView(R.layout.presetreverbtest); + + mSessionText = (EditText) findViewById(R.id.sessionEdit); + mSessionText.setOnKeyListener(mSessionKeyListener); + + mSessionText.setText(Integer.toString(sSession)); + + mReleaseButton = (ToggleButton)findViewById(R.id.presetrvbReleaseButton); + mOnOffButton = (ToggleButton)findViewById(R.id.presetrvbOnOff); + + getEffect(sSession); + + if (mPresetReverb != null) { + mReleaseButton.setOnCheckedChangeListener(this); + mOnOffButton.setOnCheckedChangeListener(this); + // Presets + SeekBar seekBar = (SeekBar)findViewById(R.id.presetrvbParam1SeekBar); + TextView textView = (TextView)findViewById(R.id.presetrvbParam1Value); + mParameters[0] = new PresetParam(mPresetReverb, (short)0, (short)(sPresetNames.length - 1), seekBar, textView); + seekBar.setOnSeekBarChangeListener(mParameters[0]); + } + } + + @Override + public void onResume() { + super.onResume(); + } + + @Override + public void onPause() { + super.onPause(); + } + + private View.OnKeyListener mSessionKeyListener + = new View.OnKeyListener() { + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_CENTER: + case KeyEvent.KEYCODE_ENTER: + try { + sSession = Integer.parseInt(mSessionText.getText().toString()); + getEffect(sSession); + if (mPresetReverb != null) { + for (int i = 0 ; i < mParameters.length; i++) { + mParameters[i].setEffect(mPresetReverb); + mParameters[i].setEnabled(true); + mParameters[i].updateDisplay(); + } + } + } catch (NumberFormatException e) { + Log.d(TAG, "Invalid session #: "+mSessionText.getText().toString()); + } + + return true; + } + } + return false; + } + }; + + // OnCheckedChangeListener + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (buttonView.getId() == R.id.presetrvbOnOff) { + if (mPresetReverb != null) { + mPresetReverb.setEnabled(isChecked); + updateParams(); + } + } + if (buttonView.getId() == R.id.presetrvbReleaseButton) { + if (isChecked) { + if (mPresetReverb == null) { + getEffect(sSession); + if (mPresetReverb != null) { + for (int i = 0 ; i < mParameters.length; i++) { + mParameters[i].setEffect(mPresetReverb); + mParameters[i].setEnabled(true); + mParameters[i].updateDisplay(); + } + } + } + } else { + if (mPresetReverb != null) { + for (int i = 0 ; i < mParameters.length; i++) { + mParameters[i].setEnabled(false); + } + putEffect(sSession); + } + } + } + } + + private class PresetParam extends EffectParameter { + private PresetReverb mPresetReverb; + + public PresetParam(PresetReverb presetrvb, short min, short max, SeekBar seekBar, TextView textView) { + super (min, max, seekBar, textView, ""); + + mPresetReverb = presetrvb; + updateDisplay(); + } + + @Override + public void setParameter(Integer value) { + if (mPresetReverb != null) { + mPresetReverb.setPreset(value.shortValue()); + } + } + + @Override + public Integer getParameter() { + if (mPresetReverb != null) { + return new Integer(mPresetReverb.getPreset()); + } + return new Integer(0); + } + + @Override + public void displayValue(int value, boolean fromTouch) { + mValueText.setText(sPresetNames[value]); + if (!fromTouch) { + mSeekBar.setProgress(value - mMin); + } else { + updateParams(); + } + } + + @Override + public void setEffect(Object presetrvb) { + mPresetReverb = (PresetReverb)presetrvb; + } + + } + + protected void updateParams() { + for (int i = 0 ; i < mParameters.length; i++) { + mParameters[i].updateDisplay(); + } + } + + public class EffectListner implements AudioEffect.OnEnableStatusChangeListener, + AudioEffect.OnControlStatusChangeListener, + PresetReverb.OnParameterChangeListener + { + public EffectListner() { + } + public void onEnableStatusChange(AudioEffect effect, boolean enabled) { + Log.d(TAG,"onEnableStatusChange: "+ enabled); + } + public void onControlStatusChange(AudioEffect effect, boolean controlGranted) { + Log.d(TAG,"onControlStatusChange: "+ controlGranted); + } + + public void onParameterChange(PresetReverb effect, int status, int param, short value) { + Log.d(TAG,"onParameterChange, status: "+status+" p: "+param+" v: "+value); + } + + private int byteArrayToInt(byte[] valueBuf, int offset) { + ByteBuffer converter = ByteBuffer.wrap(valueBuf); + converter.order(ByteOrder.nativeOrder()); + return converter.getInt(offset); + + } + private short byteArrayToShort(byte[] valueBuf, int offset) { + ByteBuffer converter = ByteBuffer.wrap(valueBuf); + converter.order(ByteOrder.nativeOrder()); + return converter.getShort(offset); + + } + } + + private void getEffect(int session) { + synchronized (sInstances) { + if (sInstances.containsKey(session)) { + mPresetReverb = sInstances.get(session); + } else { + try{ + mPresetReverb = new PresetReverb(0, session); + } catch (IllegalArgumentException e) { + Log.e(TAG,"PresetReverb effect not supported"); + } catch (UnsupportedOperationException e) { + Log.e(TAG,"PresetReverb library not loaded"); + } catch (RuntimeException e) { + Log.e(TAG,"PresetReverb effect not found"); + } + sInstances.put(session, mPresetReverb); + } + } + mReleaseButton.setEnabled(false); + mOnOffButton.setEnabled(false); + + if (mPresetReverb != null) { + if (mSettings != "") { + mPresetReverb.setProperties(new PresetReverb.Settings(mSettings)); + } + mPresetReverb.setEnableStatusListener(mEffectListener); + mPresetReverb.setControlStatusListener(mEffectListener); + mPresetReverb.setParameterListener(mEffectListener); + + mReleaseButton.setChecked(true); + mReleaseButton.setEnabled(true); + + mOnOffButton.setChecked(mPresetReverb.getEnabled()); + mOnOffButton.setEnabled(true); + } + } + + private void putEffect(int session) { + mOnOffButton.setChecked(false); + mOnOffButton.setEnabled(false); + synchronized (sInstances) { + if (mPresetReverb != null) { + mSettings = mPresetReverb.getProperties().toString(); + mPresetReverb.release(); + Log.d(TAG,"PresetReverb released"); + mPresetReverb = null; + sInstances.remove(session); + } + } + } +} diff --git a/media/tests/EffectsTest/src/com/android/effectstest/SimplePlayer.java b/media/tests/EffectsTest/src/com/android/effectstest/SimplePlayer.java new file mode 100644 index 0000000..119a604 --- /dev/null +++ b/media/tests/EffectsTest/src/com/android/effectstest/SimplePlayer.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2009 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 com.android.effectstest; + +import android.content.Context; +import android.content.res.AssetFileDescriptor; +import android.os.Bundle; +import android.util.Log; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.View.OnClickListener; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; +import android.widget.EditText; +import android.widget.SeekBar; +import android.widget.ToggleButton; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.media.AudioManager; +import android.media.MediaPlayer; +import java.nio.ByteOrder; +import java.nio.ByteBuffer; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class SimplePlayer implements OnClickListener { + + private final static String TAG = "SimplePlayer"; + + int mPlayPauseButtonId; + int mStopButtonId; + Context mContext; + ImageView mPlayPauseButton; + int mPlayImageResource; + int mPauseImageResource; + String mFileName; + int mFileResId; + MediaPlayer mMediaPlayer; + int mStreamType; + int mSession; + float mSendLevel = (float)0.5; + int mEffectId = 0; + TextView mSessionText; + + SimplePlayer(Context context, int playPausebuttonId, ImageView playPausebutton, + int stopButtonId, ImageView stopButton, TextView sessionText, String fileName, int stream, int session) + { + set(context, playPausebuttonId, playPausebutton, stopButtonId, stopButton, sessionText, stream, session); + mFileName = fileName; + } + + SimplePlayer(Context context, int playPausebuttonId, ImageView playPausebutton, + int stopButtonId, ImageView stopButton, TextView sessionText, int fileResId, int stream, int session) { + set(context, playPausebuttonId, playPausebutton, stopButtonId, stopButton, sessionText, stream, session); + mFileResId = fileResId; + mFileName = ""; + } + + public void set(Context context, int playPausebuttonId, ImageView playPausebutton, + int stopButtonId, ImageView stopButton, TextView sessionText, int stream, int session) { + mContext = context; + mPlayPauseButtonId = playPausebuttonId; + mStopButtonId = stopButtonId; + mPlayPauseButton = (ImageButton) playPausebutton; + ImageButton stop = (ImageButton) stopButton; + + mPlayPauseButton.setOnClickListener(this); + mPlayPauseButton.requestFocus(); + stop.setOnClickListener(this); + + mPlayImageResource = android.R.drawable.ic_media_play; + mPauseImageResource = android.R.drawable.ic_media_pause; + mStreamType = stream; + mSession = session; + mSessionText = sessionText; + } + + + public void onClick(View v) { + if (v.getId() == mPlayPauseButtonId) { + playOrPause(); + } else if (v.getId() == mStopButtonId) { + stop(); + } + } + + public void playOrPause() { + if (mMediaPlayer == null || !mMediaPlayer.isPlaying()){ + if (mMediaPlayer == null) { + try { + mMediaPlayer = new MediaPlayer(); + if (mSession != 0) { + mMediaPlayer.setAudioSessionId(mSession); + Log.d(TAG, "mMediaPlayer.setAudioSessionId(): "+ mSession); + } + + if (mFileName.equals("")) { + Log.d(TAG, "Playing from resource"); + AssetFileDescriptor afd = mContext.getResources().openRawResourceFd(mFileResId); + mMediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); + afd.close(); + } else { + Log.d(TAG, "Playing file: "+mFileName); + mMediaPlayer.setDataSource(mFileName); + } + mMediaPlayer.setAudioStreamType(mStreamType); + mMediaPlayer.prepare(); + mMediaPlayer.setLooping(true); + } catch (IOException ex) { + Log.e(TAG, "mMediaPlayercreate failed:", ex); + mMediaPlayer = null; + } catch (IllegalArgumentException ex) { + Log.e(TAG, "mMediaPlayercreate failed:", ex); + mMediaPlayer = null; + } catch (SecurityException ex) { + Log.e(TAG, "mMediaPlayercreate failed:", ex); + mMediaPlayer = null; + } + + if (mMediaPlayer != null) { + mMediaPlayer.setAuxEffectSendLevel(mSendLevel); + mMediaPlayer.attachAuxEffect(mEffectId); + mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { + public void onCompletion(MediaPlayer mp) { + updatePlayPauseButton(); + } + }); + mSessionText.setText("Session: "+Integer.toString(mMediaPlayer.getAudioSessionId())); + } + } + if (mMediaPlayer != null) { + mMediaPlayer.start(); + } + } else { + mMediaPlayer.pause(); + } + updatePlayPauseButton(); + } + + public void stop() { + if (mMediaPlayer != null) { + mMediaPlayer.stop(); + mMediaPlayer.release(); + mMediaPlayer = null; + } + updatePlayPauseButton(); + } + + public boolean isPlaying() { + if (mMediaPlayer != null) { + return mMediaPlayer.isPlaying(); + } else { + return false; + } + } + + public void updatePlayPauseButton() { + mPlayPauseButton.setImageResource(isPlaying() ? mPauseImageResource : mPlayImageResource); + } + + public void attachAuxEffect(int effectId) { + mEffectId = effectId; + if (mMediaPlayer != null) { + Log.d(TAG,"attach effect: "+effectId); + mMediaPlayer.attachAuxEffect(effectId); + } + } + public void setAuxEffectSendLevel(float level) { + mSendLevel = level; + if (mMediaPlayer != null) { + mMediaPlayer.setAuxEffectSendLevel(level); + } + } + + public void setContext(Context context) { + mContext = context; + } +} diff --git a/media/tests/EffectsTest/src/com/android/effectstest/VirtualizerTest.java b/media/tests/EffectsTest/src/com/android/effectstest/VirtualizerTest.java new file mode 100755 index 0000000..bb32e6f --- /dev/null +++ b/media/tests/EffectsTest/src/com/android/effectstest/VirtualizerTest.java @@ -0,0 +1,271 @@ +/* + * Copyright (C) 2009 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 com.android.effectstest; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.View.OnClickListener; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; +import android.widget.EditText; +import android.widget.SeekBar; +import android.widget.ToggleButton; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import java.nio.ByteOrder; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; + +import android.media.audiofx.Virtualizer; +import android.media.audiofx.AudioEffect; + +public class VirtualizerTest extends Activity implements OnCheckedChangeListener { + + private final static String TAG = "VirtualizerTest"; + + private static int NUM_PARAMS = 1; + + private EffectParameter mStrength; + private Virtualizer mVirtualizer; + ToggleButton mOnOffButton; + ToggleButton mReleaseButton; + EditText mSessionText; + static int sSession = 0; + EffectListner mEffectListener = new EffectListner(); + private static HashMap<Integer, Virtualizer> sInstances = new HashMap<Integer, Virtualizer>(10); + String mSettings = ""; + + public VirtualizerTest() { + Log.d(TAG, "contructor"); + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + SeekBar seekBar; + TextView textView; + + setContentView(R.layout.virtualizertest); + + mSessionText = (EditText) findViewById(R.id.sessionEdit); + mSessionText.setOnKeyListener(mSessionKeyListener); + mSessionText.setText(Integer.toString(sSession)); + + mReleaseButton = (ToggleButton)findViewById(R.id.virtReleaseButton); + mOnOffButton = (ToggleButton)findViewById(R.id.virtualizerOnOff); + + getEffect(sSession); + + if (mVirtualizer != null) { + mReleaseButton.setOnCheckedChangeListener(this); + mOnOffButton.setOnCheckedChangeListener(this); + textView = (TextView)findViewById(R.id.virtStrengthMin); + textView.setText("0"); + textView = (TextView)findViewById(R.id.virtStrengthMax); + textView.setText("1000"); + seekBar = (SeekBar)findViewById(R.id.virtStrengthSeekBar); + textView = (TextView)findViewById(R.id.virtStrengthValue); + mStrength = new VirtualizerParam(mVirtualizer, 0, 1000, seekBar, textView); + seekBar.setOnSeekBarChangeListener(mStrength); + mStrength.setEnabled(mVirtualizer.getStrengthSupported()); + } + } + + @Override + public void onResume() { + super.onResume(); + } + + @Override + public void onPause() { + super.onPause(); + } + + private View.OnKeyListener mSessionKeyListener + = new View.OnKeyListener() { + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_CENTER: + case KeyEvent.KEYCODE_ENTER: + try { + sSession = Integer.parseInt(mSessionText.getText().toString()); + getEffect(sSession); + if (mVirtualizer != null) { + mStrength.setEffect(mVirtualizer); + mStrength.setEnabled(mVirtualizer.getStrengthSupported()); + } + } catch (NumberFormatException e) { + Log.d(TAG, "Invalid session #: "+mSessionText.getText().toString()); + } + return true; + } + } + return false; + } + }; + + // OnCheckedChangeListener + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (buttonView.getId() == R.id.virtualizerOnOff) { + if (mVirtualizer != null) { + mVirtualizer.setEnabled(isChecked); + mStrength.updateDisplay(); + } + } + if (buttonView.getId() == R.id.virtReleaseButton) { + if (isChecked) { + if (mVirtualizer == null) { + getEffect(sSession); + if (mVirtualizer != null) { + mStrength.setEffect(mVirtualizer); + mStrength.setEnabled(mVirtualizer.getStrengthSupported()); + } + } + } else { + if (mVirtualizer != null) { + mStrength.setEnabled(false); + putEffect(sSession); + } + } + } + } + + private class VirtualizerParam extends EffectParameter { + private Virtualizer mVirtualizer; + + public VirtualizerParam(Virtualizer virtualizer, int min, int max, SeekBar seekBar, TextView textView) { + super (min, max, seekBar, textView, "o/oo"); + + mVirtualizer = virtualizer; + updateDisplay(); + } + + @Override + public void setParameter(Integer value) { + if (mVirtualizer != null) { + mVirtualizer.setStrength(value.shortValue()); + } + } + + @Override + public Integer getParameter() { + if (mVirtualizer != null) { + return new Integer(mVirtualizer.getRoundedStrength()); + } + return new Integer(0); + } + + @Override + public void setEffect(Object effect) { + mVirtualizer = (Virtualizer)effect; + } + } + + public class EffectListner implements AudioEffect.OnEnableStatusChangeListener, + AudioEffect.OnControlStatusChangeListener, AudioEffect.OnParameterChangeListener + { + public EffectListner() { + } + public void onEnableStatusChange(AudioEffect effect, boolean enabled) { + Log.d(TAG,"onEnableStatusChange: "+ enabled); + } + public void onControlStatusChange(AudioEffect effect, boolean controlGranted) { + Log.d(TAG,"onControlStatusChange: "+ controlGranted); + } + public void onParameterChange(AudioEffect effect, int status, byte[] param, byte[] value) { + int p = byteArrayToInt(param, 0); + short v = byteArrayToShort(value, 0); + + Log.d(TAG,"onParameterChange, status: "+status+" p: "+p+" v: "+v); + } + + private int byteArrayToInt(byte[] valueBuf, int offset) { + ByteBuffer converter = ByteBuffer.wrap(valueBuf); + converter.order(ByteOrder.nativeOrder()); + return converter.getInt(offset); + + } + private short byteArrayToShort(byte[] valueBuf, int offset) { + ByteBuffer converter = ByteBuffer.wrap(valueBuf); + converter.order(ByteOrder.nativeOrder()); + return converter.getShort(offset); + + } + } + + private void getEffect(int session) { + synchronized (sInstances) { + if (sInstances.containsKey(session)) { + mVirtualizer = sInstances.get(session); + } else { + try{ + mVirtualizer = new Virtualizer(0, session); + } catch (IllegalArgumentException e) { + Log.e(TAG,"Virtualizer effect not supported"); + } catch (IllegalStateException e) { + Log.e(TAG,"Virtualizer cannot get strength supported"); + } catch (UnsupportedOperationException e) { + Log.e(TAG,"Virtualizer library not loaded"); + } catch (RuntimeException e) { + Log.e(TAG,"Virtualizer effect not found"); + } + sInstances.put(session, mVirtualizer); + } + } + mReleaseButton.setEnabled(false); + mOnOffButton.setEnabled(false); + + if (mVirtualizer != null) { + if (mSettings != "") { + mVirtualizer.setProperties(new Virtualizer.Settings(mSettings)); + } + mVirtualizer.setEnableStatusListener(mEffectListener); + mVirtualizer.setControlStatusListener(mEffectListener); + mVirtualizer.setParameterListener(mEffectListener); + + mReleaseButton.setChecked(true); + mReleaseButton.setEnabled(true); + + mOnOffButton.setChecked(mVirtualizer.getEnabled()); + mOnOffButton.setEnabled(true); + } + } + + private void putEffect(int session) { + mOnOffButton.setChecked(false); + mOnOffButton.setEnabled(false); + synchronized (sInstances) { + if (mVirtualizer != null) { + mSettings = mVirtualizer.getProperties().toString(); + mVirtualizer.release(); + Log.d(TAG,"Virtualizer released"); + mVirtualizer = null; + sInstances.remove(session); + } + } + } +} diff --git a/media/tests/EffectsTest/src/com/android/effectstest/VisualizerTest.java b/media/tests/EffectsTest/src/com/android/effectstest/VisualizerTest.java new file mode 100755 index 0000000..60583e0 --- /dev/null +++ b/media/tests/EffectsTest/src/com/android/effectstest/VisualizerTest.java @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2009 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 com.android.effectstest; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.media.audiofx.Visualizer; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.ToggleButton; +import android.widget.SeekBar; + +import java.nio.ByteOrder; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; + +public class VisualizerTest extends Activity implements OnCheckedChangeListener { + + private final static String TAG = "Visualizer Test"; + + private Visualizer mVisualizer; + ToggleButton mOnOffButton; + ToggleButton mReleaseButton; + boolean mEnabled; + EditText mSessionText; + static int sSession = 0; + int mCaptureSize; + ToggleButton mCallbackButton; + boolean mCallbackOn; + VisualizerListener mVisualizerListener; + private static HashMap<Integer, Visualizer> sInstances = new HashMap<Integer, Visualizer>(10); + private VisualizerTestHandler mVisualizerTestHandler = null; + + public VisualizerTest() { + Log.d(TAG, "contructor"); + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + TextView textView; + + setContentView(R.layout.visualizertest); + + mSessionText = (EditText) findViewById(R.id.sessionEdit); + mSessionText.setOnKeyListener(mSessionKeyListener); + mSessionText.setText(Integer.toString(sSession)); + + mReleaseButton = (ToggleButton)findViewById(R.id.visuReleaseButton); + mOnOffButton = (ToggleButton)findViewById(R.id.visualizerOnOff); + mCallbackButton = (ToggleButton)findViewById(R.id.visuCallbackOnOff); + mCallbackOn = false; + mCallbackButton.setChecked(mCallbackOn); + + mVisualizerTestHandler = new VisualizerTestHandler(); + mVisualizerListener = new VisualizerListener(); + + getEffect(sSession); + + if (mVisualizer != null) { + mReleaseButton.setOnCheckedChangeListener(this); + mOnOffButton.setOnCheckedChangeListener(this); + mCallbackButton.setOnCheckedChangeListener(this); + } + } + + private static final int MSG_START_CAPTURE = 0; + private static final int MSG_STOP_CAPTURE = 1; + private static final int MSG_NEW_CAPTURE = 2; + private static final int CAPTURE_PERIOD_MS = 100; + + private class VisualizerTestHandler extends Handler { + boolean mActive = false; + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_START_CAPTURE: + if (!mActive) { + Log.d(TAG, "Start capture"); + mActive = true; + sendMessageDelayed(obtainMessage(MSG_NEW_CAPTURE, 0, 0, null), CAPTURE_PERIOD_MS); + } + break; + case MSG_STOP_CAPTURE: + if (mActive) { + Log.d(TAG, "Stop capture"); + mActive = false; + } + break; + case MSG_NEW_CAPTURE: + if (mActive && mVisualizer != null) { + if (mCaptureSize > 0) { + byte[] data = new byte[mCaptureSize]; + if (mVisualizer.getWaveForm(data) == Visualizer.SUCCESS) { + int len = data.length < mCaptureSize ? data.length : mCaptureSize; + displayVal(R.id.waveformMin, data[0]); + displayVal(R.id.waveformMax, data[len-1]); + displayVal(R.id.waveformCenter, data[len/2]); + }; + if (mVisualizer.getFft(data) == Visualizer.SUCCESS) { + int len = data.length < mCaptureSize ? data.length : mCaptureSize; + displayVal(R.id.fftMin, data[0]); + displayVal(R.id.fftMax, data[len-1]); + displayVal(R.id.fftCenter, data[len/2]); + }; + } + sendMessageDelayed(obtainMessage(MSG_NEW_CAPTURE, 0, 0, null), CAPTURE_PERIOD_MS); + } + break; + } + } + } + + private class VisualizerListener implements Visualizer.OnDataCaptureListener { + + public VisualizerListener() { + } + public void onWaveFormDataCapture(Visualizer visualizer, byte[] waveform, int samplingRate) { + if (visualizer == mVisualizer) { + if (waveform.length > 0) { + Log.d(TAG, "onWaveFormDataCapture(): "+waveform[0]+" smp rate: "+samplingRate/1000); + displayVal(R.id.waveformMin, waveform[0]); + displayVal(R.id.waveformMax, waveform[waveform.length - 1]); + displayVal(R.id.waveformCenter, waveform[waveform.length/2]); + } + } + } + public void onFftDataCapture(Visualizer visualizer, byte[] fft, int samplingRate) { + if (visualizer == mVisualizer) { + if (fft.length > 0) { + Log.d(TAG, "onFftDataCapture(): "+fft[0]); + displayVal(R.id.fftMin, fft[0]); + displayVal(R.id.fftMax, fft[fft.length - 1]); + displayVal(R.id.fftCenter, fft[fft.length/2]); + } + } + } + } + + @Override + public void onResume() { + super.onResume(); + } + + @Override + public void onPause() { + super.onPause(); + } + + private View.OnKeyListener mSessionKeyListener + = new View.OnKeyListener() { + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_CENTER: + case KeyEvent.KEYCODE_ENTER: + try { + sSession = Integer.parseInt(mSessionText.getText().toString()); + getEffect(sSession); + } catch (NumberFormatException e) { + Log.d(TAG, "Invalid session #: "+mSessionText.getText().toString()); + } + + return true; + } + } + return false; + } + }; + + // OnCheckedChangeListener + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (buttonView.getId() == R.id.visualizerOnOff) { + if (mVisualizer != null) { + mEnabled = isChecked; + mCallbackButton.setEnabled(!mEnabled); + if (mCallbackOn && mEnabled) { + mVisualizer.setDataCaptureListener(mVisualizerListener, + 10000, + true, + true); + } + mVisualizer.setEnabled(mEnabled); + if (mCallbackOn) { + if (!mEnabled) { + mVisualizer.setDataCaptureListener(null, + 10000, + false, + false); + } + } else { + int msg = isChecked ? MSG_START_CAPTURE : MSG_STOP_CAPTURE; + mVisualizerTestHandler.sendMessage( + mVisualizerTestHandler.obtainMessage(msg, 0, 0, null)); + } + } + } + if (buttonView.getId() == R.id.visuReleaseButton) { + if (isChecked) { + if (mVisualizer == null) { + getEffect(sSession); + } + } else { + if (mVisualizer != null) { + putEffect(sSession); + } + } + } + if (buttonView.getId() == R.id.visuCallbackOnOff) { + mCallbackOn = isChecked; + } + } + + private void displayVal(int viewId, int val) { + TextView textView = (TextView)findViewById(viewId); + String text = Integer.toString(val); + textView.setText(text); + } + + + private void getEffect(int session) { + synchronized (sInstances) { + if (sInstances.containsKey(session)) { + mVisualizer = sInstances.get(session); + } else { + try{ + mVisualizer = new Visualizer(session); + } catch (UnsupportedOperationException e) { + Log.e(TAG,"Visualizer library not loaded"); + throw (new RuntimeException("Cannot initialize effect")); + } catch (RuntimeException e) { + throw e; + } + sInstances.put(session, mVisualizer); + } + } + mReleaseButton.setEnabled(false); + mOnOffButton.setEnabled(false); + if (mVisualizer != null) { + mCaptureSize = mVisualizer.getCaptureSize(); + + mReleaseButton.setChecked(true); + mReleaseButton.setEnabled(true); + + mEnabled = mVisualizer.getEnabled(); + mOnOffButton.setChecked(mEnabled); + mOnOffButton.setEnabled(true); + + mCallbackButton.setEnabled(!mEnabled); + } + } + + private void putEffect(int session) { + mOnOffButton.setChecked(false); + mOnOffButton.setEnabled(false); + synchronized (sInstances) { + if (mVisualizer != null) { + mVisualizer.release(); + Log.d(TAG,"Visualizer released"); + mVisualizer = null; + sInstances.remove(session); + } + } + } + +} diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkPerfTestRunner.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkPerfTestRunner.java index a6cf355..3d5905d 100755 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkPerfTestRunner.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkPerfTestRunner.java @@ -38,7 +38,7 @@ import android.util.Log; public class MediaFrameworkPerfTestRunner extends InstrumentationTestRunner { public static boolean mGetNativeHeapDump = false; - + public static boolean mGetProcmem = false; @Override public TestSuite getAllTests() { @@ -61,6 +61,12 @@ public class MediaFrameworkPerfTestRunner extends InstrumentationTestRunner { if (get_heap_dump != null) { mGetNativeHeapDump = true; } + + String get_procmem = (String) icicle.get("get_procmem"); + if (get_procmem != null) { + mGetProcmem = true; + } + } } diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkTest.java index e7f98de..ed9bb97 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkTest.java @@ -98,7 +98,9 @@ public class MediaFrameworkTest extends Activity implements SurfaceHolder.Callba mWakeLock.acquire(); } - public void onStop(Bundle icicle) { + @Override + public void onDestroy() { + super.onDestroy(); mWakeLock.release(); } diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaRecorderStressTestRunner.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaRecorderStressTestRunner.java index 95e7b5e..5c74552 100755 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaRecorderStressTestRunner.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaRecorderStressTestRunner.java @@ -33,18 +33,16 @@ public class MediaRecorderStressTestRunner extends InstrumentationTestRunner { // the test must be supported by the corresponding camera. public static int mCameraId = 0; public static int mProfileQuality = CamcorderProfile.QUALITY_HIGH; - public static CamcorderProfile profile = - CamcorderProfile.get(mCameraId, mProfileQuality); - - public static int mIterations = 100; + public static CamcorderProfile profile = CamcorderProfile.get(mCameraId, mProfileQuality); + public static int mIterations = 15; public static int mVideoEncoder = profile.videoCodec; - public static int mAudioEncdoer = profile.audioCodec; + public static int mAudioEncoder = profile.audioCodec; public static int mFrameRate = profile.videoFrameRate; public static int mVideoWidth = profile.videoFrameWidth; public static int mVideoHeight = profile.videoFrameHeight; public static int mBitRate = profile.videoBitRate; public static boolean mRemoveVideo = true; - public static int mDuration = 10 * 1000; // 10 seconds + public static int mDuration = 60 * 1000; // 60 seconds public static int mTimeLapseDuration = 180 * 1000; // 3 minutes public static double mCaptureRate = 0.5; // 2 sec timelapse interval @@ -64,41 +62,41 @@ public class MediaRecorderStressTestRunner extends InstrumentationTestRunner { public void onCreate(Bundle icicle) { super.onCreate(icicle); String iterations = (String) icicle.get("iterations"); - String video_encoder = (String) icicle.get("video_encoder"); - String audio_encoder = (String) icicle.get("audio_encoder"); - String frame_rate = (String) icicle.get("frame_rate"); - String video_width = (String) icicle.get("video_width"); - String video_height = (String) icicle.get("video_height"); - String bit_rate = (String) icicle.get("bit_rate"); - String record_duration = (String) icicle.get("record_duration"); - String remove_videos = (String) icicle.get("remove_videos"); + String videoEncoder = (String) icicle.get("video_encoder"); + String audioEncoder = (String) icicle.get("audio_encoder"); + String frameRate = (String) icicle.get("frame_rate"); + String videoWidth = (String) icicle.get("video_width"); + String videoHeight = (String) icicle.get("video_height"); + String bitRate = (String) icicle.get("bit_rate"); + String recordDuration = (String) icicle.get("record_duration"); + String removeVideos = (String) icicle.get("remove_videos"); if (iterations != null ) { mIterations = Integer.parseInt(iterations); } - if ( video_encoder != null) { - mVideoEncoder = Integer.parseInt(video_encoder); + if (videoEncoder != null) { + mVideoEncoder = Integer.parseInt(videoEncoder); } - if ( audio_encoder != null) { - mAudioEncdoer = Integer.parseInt(audio_encoder); + if (audioEncoder != null) { + mAudioEncoder = Integer.parseInt(audioEncoder); } - if (frame_rate != null) { - mFrameRate = Integer.parseInt(frame_rate); + if (frameRate != null) { + mFrameRate = Integer.parseInt(frameRate); } - if (video_width != null) { - mVideoWidth = Integer.parseInt(video_width); + if (videoWidth != null) { + mVideoWidth = Integer.parseInt(videoWidth); } - if (video_height != null) { - mVideoHeight = Integer.parseInt(video_height); + if (videoHeight != null) { + mVideoHeight = Integer.parseInt(videoHeight); } - if (bit_rate != null) { - mBitRate = Integer.parseInt(bit_rate); + if (bitRate != null) { + mBitRate = Integer.parseInt(bitRate); } - if (record_duration != null) { - mDuration = Integer.parseInt(record_duration); + if (recordDuration != null) { + mDuration = Integer.parseInt(recordDuration); } - if (remove_videos != null) { - if (remove_videos.compareTo("true") == 0) { + if (removeVideos != null) { + if (removeVideos.compareTo("true") == 0) { mRemoveVideo = true; } else { mRemoveVideo = false; diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java index ccb0638..1c60401 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java @@ -1,12 +1,12 @@ /* * Copyright (C) 2008 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 @@ -53,7 +53,7 @@ import android.media.MediaMetadataRetriever; import com.android.mediaframeworktest.MediaProfileReader; /** - * Junit / Instrumentation - performance measurement for media player and + * Junit / Instrumentation - performance measurement for media player and * recorder * * FIXME: @@ -100,6 +100,7 @@ public class MediaPlayerPerformance extends ActivityInstrumentationTestCase2<Med super("com.android.mediaframeworktest", MediaFrameworkTest.class); } + @Override protected void setUp() throws Exception { super.setUp(); //Insert a 2 second before launching the test activity. This is @@ -109,18 +110,25 @@ public class MediaPlayerPerformance extends ActivityInstrumentationTestCase2<Med if (MediaFrameworkPerfTestRunner.mGetNativeHeapDump) MediaTestUtil.getNativeHeapDump(this.getName() + "_before"); - mProcMemWriter = new BufferedWriter(new FileWriter - (new File(MEDIA_PROCMEM_OUTPUT), true)); - mProcMemWriter.write(this.getName() + "\n"); + if (MediaFrameworkPerfTestRunner.mGetProcmem) { + mProcMemWriter = new BufferedWriter(new FileWriter + (new File(MEDIA_PROCMEM_OUTPUT), true)); + mProcMemWriter.write(this.getName() + "\n"); + } mMemWriter = new BufferedWriter(new FileWriter (new File(MEDIA_MEMORY_OUTPUT), true)); - + mMemWriter.write(this.getName() + "\n"); } + @Override protected void tearDown() throws Exception { if (MediaFrameworkPerfTestRunner.mGetNativeHeapDump) MediaTestUtil.getNativeHeapDump(this.getName() + "_after"); - mProcMemWriter.close(); + + if (MediaFrameworkPerfTestRunner.mGetProcmem) { + mProcMemWriter.close(); + } + mMemWriter.write("\n"); mMemWriter.close(); super.tearDown(); } @@ -157,6 +165,7 @@ public class MediaPlayerPerformance extends ActivityInstrumentationTestCase2<Med } private final class RawPreviewCallback implements PreviewCallback { + @Override public void onPreviewFrame(byte[] rawData, Camera camera) { mPreviewDone.open(); } @@ -285,19 +294,21 @@ public class MediaPlayerPerformance extends ActivityInstrumentationTestCase2<Med } } - public void writeProcmemInfo() throws Exception{ - String cmd = "procmem " + getMediaserverPid(); - Process p = Runtime.getRuntime().exec(cmd); - - InputStream inStream = p.getInputStream(); - InputStreamReader inReader = new InputStreamReader(inStream); - BufferedReader inBuffer = new BufferedReader(inReader); - String s; - while ((s = inBuffer.readLine()) != null) { - mProcMemWriter.write(s); - mProcMemWriter.write("\n"); + public void writeProcmemInfo() throws Exception { + if (MediaFrameworkPerfTestRunner.mGetProcmem) { + String cmd = "procmem " + getMediaserverPid(); + Process p = Runtime.getRuntime().exec(cmd); + + InputStream inStream = p.getInputStream(); + InputStreamReader inReader = new InputStreamReader(inStream); + BufferedReader inBuffer = new BufferedReader(inReader); + String s; + while ((s = inBuffer.readLine()) != null) { + mProcMemWriter.write(s); + mProcMemWriter.write("\n"); + } + mProcMemWriter.write("\n\n"); } - mProcMemWriter.write("\n\n"); } public String captureMediaserverInfo() { @@ -368,13 +379,11 @@ public class MediaPlayerPerformance extends ActivityInstrumentationTestCase2<Med boolean memoryResult = false; mStartPid = getMediaserverPid(); - mMemWriter.write("H263 Video Playback Only\n"); for (int i = 0; i < NUM_STRESS_LOOP; i++) { mediaStressPlayback(MediaNames.VIDEO_HIGHRES_H263); getMemoryWriteToLog(i); writeProcmemInfo(); } - mMemWriter.write("\n"); memoryResult = validateMemoryResult(mStartPid, mStartMemory, DECODER_LIMIT); assertTrue("H263 playback memory test", memoryResult); } @@ -385,13 +394,11 @@ public class MediaPlayerPerformance extends ActivityInstrumentationTestCase2<Med boolean memoryResult = false; mStartPid = getMediaserverPid(); - mMemWriter.write("H264 Video Playback only\n"); for (int i = 0; i < NUM_STRESS_LOOP; i++) { mediaStressPlayback(MediaNames.VIDEO_H264_AMR); getMemoryWriteToLog(i); writeProcmemInfo(); } - mMemWriter.write("\n"); memoryResult = validateMemoryResult(mStartPid, mStartMemory, DECODER_LIMIT); assertTrue("H264 playback memory test", memoryResult); } @@ -402,7 +409,6 @@ public class MediaPlayerPerformance extends ActivityInstrumentationTestCase2<Med boolean memoryResult = false; mStartPid = getMediaserverPid(); - mMemWriter.write("H263 video record only\n"); int frameRate = MediaProfileReader.getMaxFrameRateForCodec(MediaRecorder.VideoEncoder.H263); assertTrue("H263 video recording frame rate", frameRate != -1); for (int i = 0; i < NUM_STRESS_LOOP; i++) { @@ -411,7 +417,6 @@ public class MediaPlayerPerformance extends ActivityInstrumentationTestCase2<Med getMemoryWriteToLog(i); writeProcmemInfo(); } - mMemWriter.write("\n"); memoryResult = validateMemoryResult(mStartPid, mStartMemory, ENCODER_LIMIT); assertTrue("H263 record only memory test", memoryResult); } @@ -422,7 +427,6 @@ public class MediaPlayerPerformance extends ActivityInstrumentationTestCase2<Med boolean memoryResult = false; mStartPid = getMediaserverPid(); - mMemWriter.write("MPEG4 video record only\n"); int frameRate = MediaProfileReader.getMaxFrameRateForCodec(MediaRecorder.VideoEncoder.MPEG_4_SP); assertTrue("MPEG4 video recording frame rate", frameRate != -1); for (int i = 0; i < NUM_STRESS_LOOP; i++) { @@ -431,7 +435,6 @@ public class MediaPlayerPerformance extends ActivityInstrumentationTestCase2<Med getMemoryWriteToLog(i); writeProcmemInfo(); } - mMemWriter.write("\n"); memoryResult = validateMemoryResult(mStartPid, mStartMemory, ENCODER_LIMIT); assertTrue("mpeg4 record only memory test", memoryResult); } @@ -445,14 +448,12 @@ public class MediaPlayerPerformance extends ActivityInstrumentationTestCase2<Med mStartPid = getMediaserverPid(); int frameRate = MediaProfileReader.getMaxFrameRateForCodec(MediaRecorder.VideoEncoder.H263); assertTrue("H263 video recording frame rate", frameRate != -1); - mMemWriter.write("Audio and h263 video record\n"); for (int i = 0; i < NUM_STRESS_LOOP; i++) { assertTrue(stressVideoRecord(frameRate, 352, 288, MediaRecorder.VideoEncoder.H263, MediaRecorder.OutputFormat.MPEG_4, MediaNames.RECORDED_VIDEO_3GP, false)); getMemoryWriteToLog(i); writeProcmemInfo(); } - mMemWriter.write("\n"); memoryResult = validateMemoryResult(mStartPid, mStartMemory, ENCODER_LIMIT); assertTrue("H263 audio video record memory test", memoryResult); } @@ -463,13 +464,11 @@ public class MediaPlayerPerformance extends ActivityInstrumentationTestCase2<Med boolean memoryResult = false; mStartPid = getMediaserverPid(); - mMemWriter.write("Audio record only\n"); for (int i = 0; i < NUM_STRESS_LOOP; i++) { stressAudioRecord(MediaNames.RECORDER_OUTPUT); getMemoryWriteToLog(i); writeProcmemInfo(); } - mMemWriter.write("\n"); memoryResult = validateMemoryResult(mStartPid, mStartMemory, ENCODER_LIMIT); assertTrue("audio record only memory test", memoryResult); } @@ -480,13 +479,11 @@ public class MediaPlayerPerformance extends ActivityInstrumentationTestCase2<Med boolean memoryResult = false; mStartPid = getMediaserverPid(); - mMemWriter.write("Camera Preview Only\n"); for (int i = 0; i < NUM_STRESS_LOOP; i++) { stressCameraPreview(); getMemoryWriteToLog(i); writeProcmemInfo(); } - mMemWriter.write("\n"); memoryResult = validateMemoryResult(mStartPid, mStartMemory, CAMERA_LIMIT); assertTrue("camera preview memory test", memoryResult); } diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/CameraStressTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/CameraStressTest.java index ab9e36c..ed1d8fc 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/CameraStressTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/CameraStressTest.java @@ -28,6 +28,7 @@ import java.io.IOException; import java.io.Writer; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; +import java.util.List; import android.hardware.Camera; import android.hardware.Camera.PictureCallback; @@ -44,7 +45,7 @@ import com.android.mediaframeworktest.CameraStressTestRunner; import junit.framework.Assert; /** - * Junit / Instrumentation test case for the camera zoom api + * Junit / Instrumentation test case for the camera zoom and scene mode APIs * * adb shell am instrument * -e class com.android.mediaframeworktest.stress.CameraStressTest @@ -54,18 +55,22 @@ public class CameraStressTest extends ActivityInstrumentationTestCase2<MediaFram private String TAG = "CameraStressTest"; private Camera mCamera; + private static final int CAMERA_ID = 0; private static final int NUMBER_OF_ZOOM_LOOPS = 100; + private static final int NUMBER_OF_SCENE_MODE_LOOPS = 10; private static final long WAIT_GENERIC = 3 * 1000; // 3 seconds private static final long WAIT_TIMEOUT = 10 * 1000; // 10 seconds private static final long WAIT_ZOOM_ANIMATION = 5 * 1000; // 5 seconds - private static final String CAMERA_STRESS_OUTPUT = - "/sdcard/cameraStressOutput.txt"; - private static final int CAMERA_ID = 0; + private static final String CAMERA_STRESS_IMAGES_DIRECTORY = "cameraStressImages"; + private static final String CAMERA_STRESS_IMAGES_PREFIX = "camera-stress-test"; + private static final String CAMERA_STRESS_OUTPUT = "cameraStressOutput.txt"; private final CameraErrorCallback mCameraErrorCallback = new CameraErrorCallback(); private Thread mLooperThread; private Handler mHandler; + private Writer mOutput; + public CameraStressTest() { super("com.android.mediaframeworktest", MediaFrameworkTest.class); } @@ -89,6 +94,20 @@ public class CameraStressTest extends ActivityInstrumentationTestCase2<MediaFram } getActivity(); super.setUp(); + + File sdcard = Environment.getExternalStorageDirectory(); + + // Create the test images directory if it doesn't exist + File stressImagesDirectory = new File(String.format("%s/%s", sdcard, + CAMERA_STRESS_IMAGES_DIRECTORY)); + if (!stressImagesDirectory.exists()) { + stressImagesDirectory.mkdir(); + } + + // Start writing output file + File stressOutFile = new File(String.format("%s/%s",sdcard, CAMERA_STRESS_OUTPUT)); + mOutput = new BufferedWriter(new FileWriter(stressOutFile, true)); + mOutput.write(this.getName() + ":\n"); } @Override @@ -105,6 +124,9 @@ public class CameraStressTest extends ActivityInstrumentationTestCase2<MediaFram mLooperThread = null; } + mOutput.write("\n\n"); + mOutput.close(); + super.tearDown(); } @@ -127,9 +149,7 @@ public class CameraStressTest extends ActivityInstrumentationTestCase2<MediaFram private final class CameraErrorCallback implements android.hardware.Camera.ErrorCallback { public void onError(int error, android.hardware.Camera camera) { - if (error == android.hardware.Camera.CAMERA_ERROR_SERVER_DIED) { - assertTrue("Camera test mediaserver died", false); - } + fail(String.format("Camera error, code: %d", error)); } } @@ -154,49 +174,76 @@ public class CameraStressTest extends ActivityInstrumentationTestCase2<MediaFram try { Log.v(TAG, "JPEG picture taken"); - fos = new FileOutputStream(String.format("%s/zoom-test-%d.jpg", - Environment.getExternalStorageDirectory(), System.currentTimeMillis())); + fos = new FileOutputStream(String.format("%s/%s/%s-%d.jpg", + Environment.getExternalStorageDirectory(), CAMERA_STRESS_IMAGES_DIRECTORY, + CAMERA_STRESS_IMAGES_PREFIX, System.currentTimeMillis())); fos.write(data); - } - catch (FileNotFoundException e) { - Log.v(TAG, "File not found: " + e.toString()); - } - catch (IOException e) { - Log.v(TAG, "Error accessing file: " + e.toString()); - } - finally { + } catch (FileNotFoundException e) { + Log.e(TAG, "File not found: " + e.toString()); + } catch (IOException e) { + Log.e(TAG, "Error accessing file: " + e.toString()); + } finally { try { if (fos != null) { fos.close(); } - } - catch (IOException e) { - Log.v(TAG, "Error closing file: " + e.toString()); + } catch (IOException e) { + Log.e(TAG, "Error closing file: " + e.toString()); } } } }; // Helper method for cleaning up pics taken during testStressCameraZoom - private void cleanupZoomImages() { + private void cleanupStressTestImages() { try { - File sdcard = Environment.getExternalStorageDirectory(); + File stressImagesDirectory = new File(String.format("%s/%s", + Environment.getExternalStorageDirectory(), CAMERA_STRESS_IMAGES_DIRECTORY)); File[] zoomImages = null; FilenameFilter filter = new FilenameFilter() { public boolean accept(File dir, String name) { - return name.startsWith("zoom-test-"); + return name.startsWith(CAMERA_STRESS_IMAGES_PREFIX); } }; - zoomImages = sdcard.listFiles(filter); + zoomImages = stressImagesDirectory.listFiles(filter); for (File f : zoomImages) { f.delete(); } + } catch (SecurityException e) { + Log.e(TAG, "Security manager access violation: " + e.toString()); } - catch (SecurityException e) { - Log.v(TAG, "Security manager access violation: " + e.toString()); + } + + // Helper method for starting up the camera preview + private void startCameraPreview(SurfaceHolder surfaceHolder) { + try { + mCamera.setErrorCallback(mCameraErrorCallback); + mCamera.setPreviewDisplay(surfaceHolder); + mCamera.startPreview(); + Thread.sleep(WAIT_GENERIC); + } catch (IOException e) { + Log.e(TAG, "Error setting preview display: " + e.toString()); + } catch (InterruptedException e) { + Log.e(TAG, "Error waiting for preview to come up: " + e.toString()); + } catch (Exception e) { + Log.e(TAG, "Error starting up camera preview: " + e.toString()); + } + } + + // Helper method for taking a photo + private void capturePhoto() { + try { + mCamera.takePicture(shutterCallback, rawCallback, jpegCallback); + Thread.sleep(WAIT_GENERIC); + mCamera.stopPreview(); + mCamera.release(); + } catch (InterruptedException e) { + Log.e(TAG, "Error waiting for photo to be taken: " + e.toString()); + } catch (Exception e) { + Log.e(TAG, "Error capturing photo: " + e.toString()); } } @@ -205,14 +252,11 @@ public class CameraStressTest extends ActivityInstrumentationTestCase2<MediaFram public void testStressCameraZoom() throws Exception { SurfaceHolder mSurfaceHolder; mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder(); - File stressOutFile = new File(CAMERA_STRESS_OUTPUT); - Writer output = new BufferedWriter(new FileWriter(stressOutFile, true)); - output.write("Camera zoom stress:\n"); - output.write("Total number of loops: " + NUMBER_OF_ZOOM_LOOPS + "\n"); + mOutput.write("Total number of loops: " + NUMBER_OF_ZOOM_LOOPS + "\n"); try { Log.v(TAG, "Start preview"); - output.write("No of loop: "); + mOutput.write("No of loop: "); mCamera = Camera.open(CAMERA_ID); Camera.Parameters params = mCamera.getParameters(); @@ -220,9 +264,8 @@ public class CameraStressTest extends ActivityInstrumentationTestCase2<MediaFram if (!params.isSmoothZoomSupported() && !params.isZoomSupported()) { Log.v(TAG, "Device camera does not support zoom"); - assertTrue("Camera zoom stress test", false); - } - else { + fail("Camera zoom stress test failed"); + } else { Log.v(TAG, "Device camera does support zoom"); int nextZoomLevel = 0; @@ -235,11 +278,7 @@ public class CameraStressTest extends ActivityInstrumentationTestCase2<MediaFram } }); - mCamera.setErrorCallback(mCameraErrorCallback); - mCamera.setPreviewDisplay(mSurfaceHolder); - mCamera.startPreview(); - Thread.sleep(WAIT_GENERIC); - + startCameraPreview(mSurfaceHolder); params = mCamera.getParameters(); int currentZoomLevel = params.getZoom(); @@ -250,8 +289,7 @@ public class CameraStressTest extends ActivityInstrumentationTestCase2<MediaFram if (params.isSmoothZoomSupported()) { mCamera.startSmoothZoom(nextZoomLevel); - } - else { + } else { params.setZoom(nextZoomLevel); mCamera.setParameters(params); } @@ -259,23 +297,66 @@ public class CameraStressTest extends ActivityInstrumentationTestCase2<MediaFram // sleep allows for zoom animation to finish Thread.sleep(WAIT_ZOOM_ANIMATION); + capturePhoto(); - // take picture - mCamera.takePicture(shutterCallback, rawCallback, jpegCallback); - Thread.sleep(WAIT_GENERIC); - mCamera.stopPreview(); - mCamera.release(); - output.write(" ," + i); + if (i == 0) { + mOutput.write(Integer.toString(i)); + } else { + mOutput.write(", " + i); + } } } - - cleanupZoomImages(); + cleanupStressTestImages(); + } catch (Exception e) { + Log.e(TAG, e.toString()); + fail("Camera zoom stress test Exception"); } - catch (Exception e) { - assertTrue("Camera zoom stress test Exception", false); - Log.v(TAG, e.toString()); + } + + // Test case for stressing the camera scene mode feature + @LargeTest + public void testStressCameraSceneModes() throws Exception { + SurfaceHolder mSurfaceHolder; + mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder(); + + try { + mCamera = Camera.open(CAMERA_ID); + Camera.Parameters params = mCamera.getParameters(); + mCamera.release(); + List<String> supportedSceneModes = params.getSupportedSceneModes(); + assertNotNull("No scene modes supported", supportedSceneModes); + + mOutput.write("Total number of loops: " + + (NUMBER_OF_SCENE_MODE_LOOPS * supportedSceneModes.size()) + "\n"); + Log.v(TAG, "Start preview"); + mOutput.write("No of loop: "); + + for (int i = 0; i < supportedSceneModes.size(); i++) { + for (int j = 0; j < NUMBER_OF_SCENE_MODE_LOOPS; j++) { + runOnLooper(new Runnable() { + @Override + public void run() { + mCamera = Camera.open(CAMERA_ID); + } + }); + + startCameraPreview(mSurfaceHolder); + Log.v(TAG, "Setting mode to " + supportedSceneModes.get(i)); + params.setSceneMode(supportedSceneModes.get(i)); + mCamera.setParameters(params); + capturePhoto(); + + if ((i == 0) && (j == 0)) { + mOutput.write(Integer.toString(j + i * NUMBER_OF_SCENE_MODE_LOOPS)); + } else { + mOutput.write(", " + (j + i * NUMBER_OF_SCENE_MODE_LOOPS)); + } + } + } + cleanupStressTestImages(); + } catch (Exception e) { + Log.e(TAG, e.toString()); + fail("Camera scene mode test Exception"); } - output.write("\n\n"); - output.close(); } } diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaRecorderStressTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaRecorderStressTest.java index 6995c60..6eb9891 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaRecorderStressTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaRecorderStressTest.java @@ -31,6 +31,7 @@ import android.hardware.Camera; import android.media.CamcorderProfile; import android.media.MediaPlayer; import android.media.MediaRecorder; +import android.os.Environment; import android.os.Handler; import android.os.Looper; import android.test.ActivityInstrumentationTestCase2; @@ -48,26 +49,26 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me private MediaRecorder mRecorder; private Camera mCamera; + private static final int CAMERA_ID = 0; private static final int NUMBER_OF_CAMERA_STRESS_LOOPS = 100; private static final int NUMBER_OF_RECORDER_STRESS_LOOPS = 100; private static final int NUMBER_OF_RECORDERANDPLAY_STRESS_LOOPS = 50; private static final int NUMBER_OF_SWTICHING_LOOPS_BW_CAMERA_AND_RECORDER = 200; private static final int NUMBER_OF_TIME_LAPSE_LOOPS = 25; private static final int TIME_LAPSE_PLAYBACK_WAIT_TIME = 5* 1000; // 5 seconds + private static final int USE_TEST_RUNNER_PROFILE = -1; + private static final long WAIT_TIMEOUT = 10 * 1000; // 10 seconds private static final long WAIT_TIME_CAMERA_TEST = 3 * 1000; // 3 seconds private static final long WAIT_TIME_RECORDER_TEST = 6 * 1000; // 6 seconds - private static final String OUTPUT_FILE = "/sdcard/temp"; private static final String OUTPUT_FILE_EXT = ".3gp"; - private static final String MEDIA_STRESS_OUTPUT = - "/sdcard/mediaStressOutput.txt"; - private static final int CAMERA_ID = 0; + private static final String MEDIA_STRESS_OUTPUT = "mediaStressOutput.txt"; private final CameraErrorCallback mCameraErrorCallback = new CameraErrorCallback(); private final RecorderErrorCallback mRecorderErrorCallback = new RecorderErrorCallback(); - private final static int WAIT_TIMEOUT = 10 * 1000; // 10 seconds - private Thread mLooperThread; private Handler mHandler; + private Thread mLooperThread; + private Writer mOutput; public MediaRecorderStressTest() { super("com.android.mediaframeworktest", MediaFrameworkTest.class); @@ -95,6 +96,11 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me Thread.sleep(2000); getActivity(); super.setUp(); + + File stressOutFile = new File(String.format("%s/%s", + Environment.getExternalStorageDirectory(), MEDIA_STRESS_OUTPUT)); + mOutput = new BufferedWriter(new FileWriter(stressOutFile, true)); + mOutput.write(this.getName() + "\n"); } @Override @@ -110,7 +116,8 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me } mLooperThread = null; } - + mOutput.write("\n\n"); + mOutput.close(); super.tearDown(); } @@ -133,16 +140,13 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me private final class CameraErrorCallback implements android.hardware.Camera.ErrorCallback { public void onError(int error, android.hardware.Camera camera) { - if (error == android.hardware.Camera.CAMERA_ERROR_SERVER_DIED) { - assertTrue("Camera test mediaserver died", false); - } + fail(String.format("Camera error, code: %d", error)); } } private final class RecorderErrorCallback implements MediaRecorder.OnErrorListener { public void onError(MediaRecorder mr, int what, int extra) { - // fail the test case no matter what error come up - assertTrue("mediaRecorder error", false); + fail(String.format("Media recorder error, code: %d\textra: %d", what, extra)); } } @@ -151,14 +155,11 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me public void testStressCamera() throws Exception { SurfaceHolder mSurfaceHolder; mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder(); - File stressOutFile = new File(MEDIA_STRESS_OUTPUT); - Writer output = new BufferedWriter(new FileWriter(stressOutFile, true)); - output.write("Camera start preview stress:\n"); - output.write("Total number of loops:" + - NUMBER_OF_CAMERA_STRESS_LOOPS + "\n"); + Log.v(TAG, "Camera start preview stress test"); + mOutput.write("Total number of loops:" + NUMBER_OF_CAMERA_STRESS_LOOPS + "\n"); try { Log.v(TAG, "Start preview"); - output.write("No of loop: "); + mOutput.write("No of loop: "); for (int i = 0; i< NUMBER_OF_CAMERA_STRESS_LOOPS; i++) { runOnLooper(new Runnable() { @@ -173,29 +174,27 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me Thread.sleep(WAIT_TIME_CAMERA_TEST); mCamera.stopPreview(); mCamera.release(); - output.write(" ," + i); + if (i == 0) { + mOutput.write(i + 1); + } else { + mOutput.write(String.format(", %d", (i + 1))); + } } } catch (Exception e) { - assertTrue("CameraStressTest", false); - Log.v(TAG, e.toString()); + Log.e(TAG, e.toString()); + fail("Camera startup preview stress test"); } - output.write("\n\n"); - output.close(); } //Test case for stressing the camera preview. @LargeTest public void testStressRecorder() throws Exception { - String filename; SurfaceHolder mSurfaceHolder; mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder(); - File stressOutFile = new File(MEDIA_STRESS_OUTPUT); - Writer output = new BufferedWriter(new FileWriter(stressOutFile, true)); - output.write("H263 video record- reset after prepare Stress test\n"); - output.write("Total number of loops:" + - NUMBER_OF_RECORDER_STRESS_LOOPS + "\n"); + Log.v(TAG, "H263 video record: reset after prepare Stress test"); + mOutput.write("Total number of loops:" + NUMBER_OF_RECORDER_STRESS_LOOPS + "\n"); try { - output.write("No of loop: "); + mOutput.write("No of loop: "); Log.v(TAG, "Start preview"); for (int i = 0; i < NUMBER_OF_RECORDER_STRESS_LOOPS; i++) { runOnLooper(new Runnable() { @@ -205,12 +204,15 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me } }); Log.v(TAG, "counter = " + i); - filename = OUTPUT_FILE + i + OUTPUT_FILE_EXT; - Log.v(TAG, filename); + String fileName = String.format("%s/temp%d%s", + Environment.getExternalStorageDirectory(), + i, OUTPUT_FILE_EXT); + + Log.v(TAG, fileName); mRecorder.setOnErrorListener(mRecorderErrorCallback); mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); - mRecorder.setOutputFile(filename); + mRecorder.setOutputFile(fileName); mRecorder.setVideoFrameRate(MediaRecorderStressTestRunner.mFrameRate); mRecorder.setVideoSize(176,144); Log.v(TAG, "setEncoder"); @@ -224,30 +226,29 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me Thread.sleep(WAIT_TIME_RECORDER_TEST); mRecorder.reset(); mRecorder.release(); - output.write(", " + i); + if (i == 0) { + mOutput.write(i + 1); + } else { + mOutput.write(String.format(", %d", (i + 1))); + } } } catch (Exception e) { - assertTrue("Recorder Stress test", false); - Log.v(TAG, e.toString()); + Log.e(TAG, e.toString()); + fail("H263 video recording stress test"); } - output.write("\n\n"); - output.close(); } //Stress test case for switching camera and video recorder preview. @LargeTest public void testStressCameraSwitchRecorder() throws Exception { - String filename; SurfaceHolder mSurfaceHolder; mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder(); - File stressOutFile = new File(MEDIA_STRESS_OUTPUT); - Writer output = new BufferedWriter(new FileWriter(stressOutFile, true)); - output.write("Camera and video recorder preview switching\n"); - output.write("Total number of loops:" - + NUMBER_OF_SWTICHING_LOOPS_BW_CAMERA_AND_RECORDER + "\n"); + Log.v(TAG, "Camera and video recorder preview switching"); + mOutput.write("Total number of loops: " + + NUMBER_OF_SWTICHING_LOOPS_BW_CAMERA_AND_RECORDER + "\n"); try { Log.v(TAG, "Start preview"); - output.write("No of loop: "); + mOutput.write("No of loop: "); for (int i = 0; i < NUMBER_OF_SWTICHING_LOOPS_BW_CAMERA_AND_RECORDER; i++) { runOnLooper(new Runnable() { @Override @@ -263,8 +264,10 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me mCamera.release(); mCamera = null; Log.v(TAG, "release camera"); - filename = OUTPUT_FILE + i + OUTPUT_FILE_EXT; - Log.v(TAG, filename); + String fileName = String.format("%s/temp%d%s", + Environment.getExternalStorageDirectory(), + i, OUTPUT_FILE_EXT); + Log.v(TAG, fileName); runOnLooper(new Runnable() { @Override public void run() { @@ -274,7 +277,7 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me mRecorder.setOnErrorListener(mRecorderErrorCallback); mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); - mRecorder.setOutputFile(filename); + mRecorder.setOutputFile(fileName); mRecorder.setVideoFrameRate(MediaRecorderStressTestRunner.mFrameRate); mRecorder.setVideoSize(176,144); Log.v(TAG, "Media recorder setEncoder"); @@ -287,117 +290,167 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me Thread.sleep(WAIT_TIME_CAMERA_TEST); mRecorder.release(); Log.v(TAG, "release video recorder"); - output.write(", " + i); + if (i == 0) { + mOutput.write(i + 1); + } else { + mOutput.write(String.format(", %d", (i + 1))); + } } } catch (Exception e) { - assertTrue("Camer and recorder switch mode", false); - Log.v(TAG, e.toString()); + Log.e(TAG, e.toString()); + fail("Camera and recorder switch mode"); } - output.write("\n\n"); - output.close(); } - public void validateRecordedVideo(String recorded_file) { + public void validateRecordedVideo(String recordedFile) { try { MediaPlayer mp = new MediaPlayer(); - mp.setDataSource(recorded_file); + mp.setDataSource(recordedFile); mp.prepare(); int duration = mp.getDuration(); if (duration <= 0){ - assertTrue("stressRecordAndPlayback", false); + fail("stressRecordAndPlayback"); } mp.release(); } catch (Exception e) { - assertTrue("stressRecordAndPlayback", false); + fail("stressRecordAndPlayback"); } } - public void removeRecordedVideo(String filename){ - File video = new File(filename); - Log.v(TAG, "remove recorded video " + filename); + public void removeRecordedVideo(String fileName){ + File video = new File(fileName); + Log.v(TAG, "remove recorded video " + fileName); video.delete(); } - //Stress test case for record a video and play right away. - @LargeTest - public void testStressRecordVideoAndPlayback() throws Exception { - int iterations = MediaRecorderStressTestRunner.mIterations; - int video_encoder = MediaRecorderStressTestRunner.mVideoEncoder; - int audio_encoder = MediaRecorderStressTestRunner.mAudioEncdoer; - int frame_rate = MediaRecorderStressTestRunner.mFrameRate; - int video_width = MediaRecorderStressTestRunner.mVideoWidth; - int video_height = MediaRecorderStressTestRunner.mVideoHeight; - int bit_rate = MediaRecorderStressTestRunner.mBitRate; - boolean remove_video = MediaRecorderStressTestRunner.mRemoveVideo; - int record_duration = MediaRecorderStressTestRunner.mDuration; - - String filename; - SurfaceHolder mSurfaceHolder; - mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder(); - File stressOutFile = new File(MEDIA_STRESS_OUTPUT); - Writer output = new BufferedWriter( - new FileWriter(stressOutFile, true)); - output.write("Video record and play back stress test:\n"); - output.write("Total number of loops:" - + NUMBER_OF_RECORDERANDPLAY_STRESS_LOOPS + "\n"); + // Helper method for record & playback testing with different camcorder profiles + private void recordVideoAndPlayback(int profile) throws Exception { + int iterations; + int recordDuration; + boolean removeVideo; + + int videoEncoder; + int audioEncoder; + int frameRate; + int videoWidth; + int videoHeight; + int bitRate; + + if (profile != USE_TEST_RUNNER_PROFILE) { + assertTrue(String.format("Camera doesn't support profile %d", profile), + CamcorderProfile.hasProfile(CAMERA_ID, profile)); + CamcorderProfile camcorderProfile = CamcorderProfile.get(CAMERA_ID, profile); + videoEncoder = camcorderProfile.videoCodec; + audioEncoder = camcorderProfile.audioCodec; + frameRate = camcorderProfile.videoFrameRate; + videoWidth = camcorderProfile.videoFrameWidth; + videoHeight = camcorderProfile.videoFrameHeight; + bitRate = camcorderProfile.videoBitRate; + } else { + videoEncoder = MediaRecorderStressTestRunner.mVideoEncoder; + audioEncoder = MediaRecorderStressTestRunner.mAudioEncoder; + frameRate = MediaRecorderStressTestRunner.mFrameRate; + videoWidth = MediaRecorderStressTestRunner.mVideoWidth; + videoHeight = MediaRecorderStressTestRunner.mVideoHeight; + bitRate = MediaRecorderStressTestRunner.mBitRate; + } + iterations = MediaRecorderStressTestRunner.mIterations; + recordDuration = MediaRecorderStressTestRunner.mDuration; + removeVideo = MediaRecorderStressTestRunner.mRemoveVideo; + + SurfaceHolder surfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder(); + mOutput.write("Total number of loops: " + iterations + "\n"); + try { - output.write("No of loop: "); - for (int i = 0; i < iterations; i++){ - filename = OUTPUT_FILE + i + OUTPUT_FILE_EXT; - Log.v(TAG, filename); + mOutput.write("No of loop: "); + for (int i = 0; i < iterations; i++) { + String fileName = String.format("%s/temp%d%s", + Environment.getExternalStorageDirectory(), i, OUTPUT_FILE_EXT); + Log.v(TAG, fileName); + runOnLooper(new Runnable() { @Override public void run() { mRecorder = new MediaRecorder(); } }); + Log.v(TAG, "iterations : " + iterations); - Log.v(TAG, "video_encoder : " + video_encoder); - Log.v(TAG, "audio_encoder : " + audio_encoder); - Log.v(TAG, "frame_rate : " + frame_rate); - Log.v(TAG, "video_width : " + video_width); - Log.v(TAG, "video_height : " + video_height); - Log.v(TAG, "bit rate : " + bit_rate); - Log.v(TAG, "record_duration : " + record_duration); + Log.v(TAG, "video encoder : " + videoEncoder); + Log.v(TAG, "audio encoder : " + audioEncoder); + Log.v(TAG, "frame rate : " + frameRate); + Log.v(TAG, "video width : " + videoWidth); + Log.v(TAG, "video height : " + videoHeight); + Log.v(TAG, "bit rate : " + bitRate); + Log.v(TAG, "record duration : " + recordDuration); mRecorder.setOnErrorListener(mRecorderErrorCallback); mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); - mRecorder.setOutputFile(filename); - mRecorder.setVideoFrameRate(frame_rate); - mRecorder.setVideoSize(video_width, video_height); - mRecorder.setVideoEncoder(video_encoder); - mRecorder.setAudioEncoder(audio_encoder); - mRecorder.setVideoEncodingBitRate(bit_rate); + mRecorder.setOutputFile(fileName); + mRecorder.setVideoFrameRate(frameRate); + mRecorder.setVideoSize(videoWidth, videoHeight); + mRecorder.setVideoEncoder(videoEncoder); + mRecorder.setAudioEncoder(audioEncoder); + mRecorder.setVideoEncodingBitRate(bitRate); + Log.v(TAG, "mediaRecorder setPreview"); - mRecorder.setPreviewDisplay(mSurfaceHolder.getSurface()); + mRecorder.setPreviewDisplay(surfaceHolder.getSurface()); mRecorder.prepare(); mRecorder.start(); - Thread.sleep(record_duration); + Thread.sleep(recordDuration); Log.v(TAG, "Before stop"); mRecorder.stop(); mRecorder.release(); + //start the playback MediaPlayer mp = new MediaPlayer(); - mp.setDataSource(filename); + mp.setDataSource(fileName); mp.setDisplay(MediaFrameworkTest.mSurfaceView.getHolder()); mp.prepare(); mp.start(); - Thread.sleep(record_duration); + Thread.sleep(recordDuration); mp.release(); - validateRecordedVideo(filename); - if (remove_video) { - removeRecordedVideo(filename); + validateRecordedVideo(fileName); + if (removeVideo) { + removeRecordedVideo(fileName); + } + if (i == 0) { + mOutput.write(i + 1); + } else { + mOutput.write(String.format(", %d", (i + 1))); } - output.write(", " + i); } } catch (Exception e) { - Log.v(TAG, e.toString()); - assertTrue("record and playback", false); + Log.e(TAG, e.toString()); + fail("Record and playback"); } - output.write("\n\n"); - output.close(); + } + + // Record and playback stress test @ 1080P quality + @LargeTest + public void testStressRecordVideoAndPlayback1080P() throws Exception { + recordVideoAndPlayback(CamcorderProfile.QUALITY_1080P); + } + + // Record and playback stress test @ 720P quality + @LargeTest + public void testStressRecordVideoAndPlayback720P() throws Exception { + recordVideoAndPlayback(CamcorderProfile.QUALITY_720P); + } + + // Record and playback stress test @ 480P quality + @LargeTest + public void testStressRecordVideoAndPlayback480P() throws Exception { + recordVideoAndPlayback(CamcorderProfile.QUALITY_480P); + } + + // This test method uses the codec info from the test runner. Use this + // for more granular control of video encoding. + @LargeTest + public void defaultStressRecordVideoAndPlayback() throws Exception { + recordVideoAndPlayback(USE_TEST_RUNNER_PROFILE); } // Test case for stressing time lapse @@ -405,21 +458,19 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me public void testStressTimeLapse() throws Exception { SurfaceHolder mSurfaceHolder; mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder(); - int record_duration = MediaRecorderStressTestRunner.mTimeLapseDuration; - boolean remove_video = MediaRecorderStressTestRunner.mRemoveVideo; + int recordDuration = MediaRecorderStressTestRunner.mTimeLapseDuration; + boolean removeVideo = MediaRecorderStressTestRunner.mRemoveVideo; double captureRate = MediaRecorderStressTestRunner.mCaptureRate; - String filename; - File stressOutFile = new File(MEDIA_STRESS_OUTPUT); - Writer output = new BufferedWriter(new FileWriter(stressOutFile, true)); - output.write("Start camera time lapse stress:\n"); - output.write("Total number of loops: " + NUMBER_OF_TIME_LAPSE_LOOPS + "\n"); + Log.v(TAG, "Start camera time lapse stress:"); + mOutput.write("Total number of loops: " + NUMBER_OF_TIME_LAPSE_LOOPS + "\n"); try { - for (int j = 0, n = Camera.getNumberOfCameras(); j < n; j++) { - output.write("No of loop: camera " + j); - for (int i = 0; i < NUMBER_OF_TIME_LAPSE_LOOPS; i++) { - filename = OUTPUT_FILE + j + "_" + i + OUTPUT_FILE_EXT; - Log.v(TAG, filename); + for (int i = 0, n = Camera.getNumberOfCameras(); i < n; i++) { + mOutput.write("No of loop: camera " + i); + for (int j = 0; j < NUMBER_OF_TIME_LAPSE_LOOPS; j++) { + String fileName = String.format("%s/temp%d_%d%s", + Environment.getExternalStorageDirectory(), i, j, OUTPUT_FILE_EXT); + Log.v(TAG, fileName); runOnLooper(new Runnable() { @Override public void run() { @@ -438,12 +489,12 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me CamcorderProfile.get(j, CamcorderProfile.QUALITY_TIME_LAPSE_HIGH); mRecorder.setProfile(profile); - // Set the timelapse setting; 0.1 = 10 sec timelapse, 0.5 = 2 sec timelapse, etc. + // Set the timelapse setting; 0.1 = 10 sec timelapse, 0.5 = 2 sec timelapse, etc // http://developer.android.com/guide/topics/media/camera.html#time-lapse-video mRecorder.setCaptureRate(captureRate); // Set output file - mRecorder.setOutputFile(filename); + mRecorder.setOutputFile(fileName); // Set the preview display Log.v(TAG, "mediaRecorder setPreviewDisplay"); @@ -451,40 +502,40 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me mRecorder.prepare(); mRecorder.start(); - Thread.sleep(record_duration); + Thread.sleep(recordDuration); Log.v(TAG, "Before stop"); mRecorder.stop(); mRecorder.release(); // Start the playback MediaPlayer mp = new MediaPlayer(); - mp.setDataSource(filename); + mp.setDataSource(fileName); mp.setDisplay(mSurfaceHolder); mp.prepare(); mp.start(); Thread.sleep(TIME_LAPSE_PLAYBACK_WAIT_TIME); mp.release(); - validateRecordedVideo(filename); - if (remove_video) { - removeRecordedVideo(filename); + validateRecordedVideo(fileName); + if (removeVideo) { + removeRecordedVideo(fileName); + } + + if (j == 0) { + mOutput.write(j + 1); + } else { + mOutput.write(String.format(", %d", (j + 1))); } - output.write(", " + i); } } + } catch (IllegalStateException e) { + Log.e(TAG, e.toString()); + fail("Camera time lapse stress test IllegalStateException"); + } catch (IOException e) { + Log.e(TAG, e.toString()); + fail("Camera time lapse stress test IOException"); + } catch (Exception e) { + Log.e(TAG, e.toString()); + fail("Camera time lapse stress test Exception"); } - catch (IllegalStateException e) { - assertTrue("Camera time lapse stress test IllegalStateException", false); - Log.v(TAG, e.toString()); - } - catch (IOException e) { - assertTrue("Camera time lapse stress test IOException", false); - Log.v(TAG, e.toString()); - } - catch (Exception e) { - assertTrue("Camera time lapse stress test Exception", false); - Log.v(TAG, e.toString()); - } - output.write("\n\n"); - output.close(); } } diff --git a/media/tests/contents/media_api/music/sine_200+1000Hz_44K_mo.wav b/media/tests/contents/media_api/music/sine_200+1000Hz_44K_mo.wav Binary files differindex 312b6fb..24c2a0d 100755..100644 --- a/media/tests/contents/media_api/music/sine_200+1000Hz_44K_mo.wav +++ b/media/tests/contents/media_api/music/sine_200+1000Hz_44K_mo.wav |
