diff --git a/StageNowAudioVolUIMgrProfileMAXIMUM.pdf b/StageNowAudioVolUIMgrProfileMAXIMUM.pdf new file mode 100644 index 0000000..884e085 Binary files /dev/null and b/StageNowAudioVolUIMgrProfileMAXIMUM.pdf differ diff --git a/hsdemo/build.gradle b/hsdemo/build.gradle index a5bf630..85cbfa5 100644 --- a/hsdemo/build.gradle +++ b/hsdemo/build.gradle @@ -38,5 +38,5 @@ dependencies { androidTestImplementation libs.ext.junit androidTestImplementation libs.espresso.core implementation 'com.github.ltrudu:CriticalPermissionsHelper:+' - + compileOnly 'com.symbol:emdk:9.1.1' } \ No newline at end of file diff --git a/hsdemo/src/main/java/com/zebra/hsdemo/EMDKUtils.java b/hsdemo/src/main/java/com/zebra/hsdemo/EMDKUtils.java new file mode 100644 index 0000000..b51a48c --- /dev/null +++ b/hsdemo/src/main/java/com/zebra/hsdemo/EMDKUtils.java @@ -0,0 +1,484 @@ +package com.zebra.hsdemo; + +import android.content.Context; +import android.text.TextUtils; +import android.util.Log; +import android.util.Xml; + +import com.symbol.emdk.EMDKBase; +import com.symbol.emdk.EMDKException; +import com.symbol.emdk.EMDKManager; +import com.symbol.emdk.EMDKResults; +import com.symbol.emdk.ProfileManager; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.StringReader; +import java.util.ArrayList; + +public class EMDKUtils { + + public interface IResultCallbacks{ + void onSuccess(final String message, final String resultXML); + void onError(final String message, final String resultXML); + void onDebugStatus(final String message); + } + + protected enum EMessageType { + VERBOSE("VERBOSE"), + WARNING("WARNING"), + ERROR("ERROR"), + SUCCESS("SUCCESS"), + DEBUG("DEBUG"); + + String stringContent = ""; + EMessageType(String stringContent) { this.stringContent = stringContent;} + + public String toString() { + return stringContent; + } + + public static EMessageType fromString(String messageType) + { + switch(messageType) { + case "VERBOSE": + return VERBOSE; + case "WARNING": + return WARNING; + case "Error": + return ERROR; + case "SUCCESS": + return SUCCESS; + case "DEBUG": + return DEBUG; + default: + return null; + } + } + } + + // A class that will hold errors if it happens + protected class ErrorHolder + { + // Provides the error type for characteristic-error + protected String sErrorType = ""; + + // Provides the parm name for parm-error + protected String sParmName = ""; + + // Provides error description + protected String sErrorDescription = ""; + } + + private final static String TAG = "EMDKUtils"; + + // Profile content in XML + private String msProfileData = ""; + + // Profile name to execute + private String msProfileName = ""; + + //Declare a variable to store ProfileManager object + private ProfileManager mProfileManager = null; + + //Declare a variable to store EMDKManager object + private EMDKManager mEMDKManager = null; + + // An ArrayList that will contains errors if we find some + private ArrayList mErrors = new ArrayList<>(); + + // Error String + private String msErrorString = null; + + // Status returned by the profile in case of success + private String msStatusXMLResponse = ""; + + private Context mContext = null; + + private IResultCallbacks mIResultCallbacks = null; + + // EMDKListener implementation + private EMDKManager.EMDKListener mEMDKListener = new EMDKManager.EMDKListener() { + @Override + public void onOpened(EMDKManager emdkManager) { + logMessage("EMDKManager.EMDKListener.onOpened", EMessageType.DEBUG); + onEMDKManagerRetrieved(emdkManager); + } + + @Override + public void onClosed() { + logMessage("EMDKManager.EMDKListener.onClosed", EMessageType.DEBUG); + onEMDKManagerClosed(); + } + }; + + // Status Listener implementation (ensure that we retrieve the profile manager asynchronously + private EMDKManager.StatusListener mStatusListener = new EMDKManager.StatusListener() { + @Override + public void onStatus(EMDKManager.StatusData statusData, EMDKBase emdkBase) { + if(statusData.getResult() == EMDKResults.STATUS_CODE.SUCCESS) + { + ProfileManager profileManager = (ProfileManager)emdkBase; + if(profileManager != null) + onProfileManagerInitialized(profileManager); + else + { + logMessage("Casting error when retrieving ProfileManager.", EMessageType.ERROR); + profileManager = (ProfileManager) mEMDKManager.getInstance(EMDKManager.FEATURE_TYPE.PROFILE); + if(profileManager != null) { + logMessage("Profile manager retrieved synchronously with success", EMessageType.VERBOSE); + onProfileManagerInitialized(profileManager); + } + } + } + else + { + String errorMessage = "Error when trying to retrieve ProfileManager: " + getResultCode(statusData.getResult()); + logMessage(errorMessage, EMessageType.ERROR); + } + } + }; + + public EMDKUtils(Context context) + { + mContext = context; + } + + public void activateVolumeProfile(String audioProfileName, IResultCallbacks resultCallbacks) + { + mIResultCallbacks = resultCallbacks; + // Create profile content + msProfileName = "AudioVolumeMgr-1"; + msProfileData = "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n" + + "\n"; + + // initialize profile manager processing + initializeEMDK(); + } + + private void initializeEMDK() + { + if(mEMDKManager == null) + { + EMDKResults results = null; + try + { + //The EMDKManager object will be created and returned in the callback. + results = EMDKManager.getEMDKManager(mContext.getApplicationContext(), mEMDKListener); + } + catch(Exception e) + { + logMessage("Error while requesting EMDKManager.\n" + e.getLocalizedMessage(), EMessageType.ERROR); + e.printStackTrace(); + return; + } + + //Check the return status of EMDKManager object creation. + if(results.statusCode == EMDKResults.STATUS_CODE.SUCCESS) { + logMessage("EMDKManager request command issued with success", EMessageType.DEBUG); + }else { + logMessage("EMDKManager request command error", EMessageType.ERROR); + } + } + else + { + onEMDKManagerRetrieved(mEMDKManager); + } + } + + private void onEMDKManagerRetrieved(EMDKManager emdkManager) + { + mEMDKManager = emdkManager; + logMessage("EMDK Manager retrieved.", EMessageType.DEBUG); + if(mProfileManager == null) + { + try { + logMessage("Requesting profile manager.", EMessageType.DEBUG); + logMessage("Current API version: " + android.os.Build.VERSION.SDK_INT, EMessageType.VERBOSE); + if(android.os.Build.VERSION.SDK_INT < 33) { + logMessage("Requesting profile manager Asynchonously", EMessageType.DEBUG); + emdkManager.getInstanceAsync(EMDKManager.FEATURE_TYPE.PROFILE, mStatusListener); + } + else + { + logMessage("Requesting profile manager synchronized", EMessageType.DEBUG); + ProfileManager profileManager = (ProfileManager) emdkManager.getInstance(EMDKManager.FEATURE_TYPE.PROFILE); + if(profileManager != null) + { + onProfileManagerInitialized(profileManager); + } + } + } catch (EMDKException e) { + logMessage("Error when trying to retrieve profile manager: " + e.getMessage(), EMessageType.ERROR); + } + } + else + { + logMessage("EMDK Manager already initialized.", EMessageType.DEBUG); + onProfileManagerInitialized(mProfileManager); + } + } + + private void onProfileManagerInitialized(ProfileManager profileManager) + { + mProfileManager = profileManager; + logMessage("Profile Manager retrieved.", EMessageType.DEBUG); + processMXContent(); + } + + private void processMXContent() + { + String[] params = new String[1]; + params[0] = msProfileData; + + if(mProfileManager == null) + { + logMessage("ProcessMXContent : Error : ProfileManager == null", EMessageType.ERROR); + if(mEMDKManager != null) { + logMessage("ProcessMXContent : Trying to retrieve profileManager synchronously", EMessageType.ERROR); + ProfileManager profileManager = (ProfileManager) mEMDKManager.getInstance(EMDKManager.FEATURE_TYPE.PROFILE); + if (profileManager != null) { + logMessage("ProcessMXContent, ProfileManager retrieved syncrhonously.", EMessageType.VERBOSE); + mProfileManager = profileManager; + } + else + { + logMessage("ProcessMXContent : Error : Could not retrieve ProfileManager syncrhonously.", EMessageType.VERBOSE); + onProfileExecutedError("ProcessMXContent : Error : Could not retrieve ProfileManager syncrhonously."); + return; + } + } + else { + logMessage("ProcessMXContent : Error : mEMDKManager == null", EMessageType.ERROR); + onProfileExecutedError("ProcessMXContent : Error : mEMDKManager == null"); + return; + } + } + + EMDKResults results = mProfileManager.processProfile(msProfileName, ProfileManager.PROFILE_FLAG.SET, params); + + //Check the return status of processProfile + if(results.statusCode == EMDKResults.STATUS_CODE.CHECK_XML) { + + // Get XML response as a String + msStatusXMLResponse = results.getStatusString(); + + try { + // Empty Error Holder Array List if it already exists + mErrors.clear(); + + // Create instance of XML Pull Parser to parse the response + XmlPullParser parser = Xml.newPullParser(); + // Provide the string response to the String Reader that reads + // for the parser + parser.setInput(new StringReader(msStatusXMLResponse)); + // Call method to parse the response + parseXML(parser); + + if ( mErrors.size() == 0 ) { + + logMessage("Profile executed with success: " + msProfileName, EMessageType.SUCCESS); + onProfileExecutedWithSuccess(); + } + else { + String errorMessage = ""; + for(ErrorHolder error : mErrors) + { + errorMessage += "Profile processing error.\t" + "Type:" + error.sErrorType + "\tParamName:" + error.sParmName + "\tDescription:" + error.sErrorDescription; + } + logMessage(errorMessage, EMessageType.ERROR); + onProfileExecutedError(errorMessage); + return; + } + + } catch (XmlPullParserException e) { + String errorMessage = "Error while trying to parse ProfileManager XML Response: " + e.getLocalizedMessage(); + logMessage(errorMessage, EMessageType.ERROR); + onProfileExecutedError(errorMessage); + return; + } + } + else if(results.statusCode == EMDKResults.STATUS_CODE.SUCCESS) + { + logMessage("Profile executed with success: " + msProfileName, EMessageType.DEBUG); + onProfileExecutedWithSuccess(); + return; + } + else + { + String errorMessage = "Profile update failed." + getResultCode(results.statusCode) + "\nProfil:\n" + msProfileName; + logMessage(errorMessage, EMessageType.ERROR); + onProfileExecutedError(errorMessage); + return; + } + } + + private void onProfileExecutedWithSuccess() + { + cleanUp(); + if(mIResultCallbacks != null) + { + mIResultCallbacks.onSuccess("Success applying profile:" + msProfileName + "\nProfileData:" + msProfileData, msStatusXMLResponse); + } + + } + + private void onProfileExecutedError(String message) + { + cleanUp(); + if(mIResultCallbacks != null) + { + mIResultCallbacks.onError("Error on profile: " + msProfileName + "\nError:" + message + "\nProfileData:" + msProfileData, msStatusXMLResponse); + } + + } + + private void onProfileExecutedStatusChanged(String message) + { + if(mIResultCallbacks != null) + { + mIResultCallbacks.onDebugStatus(message); + } + } + + // Method to parse the XML response using XML Pull Parser + private void parseXML(XmlPullParser myParser) { + int event; + try { + // Retrieve error details if parm-error/characteristic-error in the response XML + event = myParser.getEventType(); + // An object that will store a temporary error holder if an error characteristic is found + ErrorHolder tempErrorHolder = null; + //logMessage("XML document", EMessageType.VERBOSE); + while (event != XmlPullParser.END_DOCUMENT) { + String name = myParser.getName(); + switch (event) { + case XmlPullParser.START_TAG: + //logMessage("XML Element:<" + myParser.getText()+">", EMessageType.VERBOSE); + if (name.equals("characteristic-error")) + { + if(tempErrorHolder == null) + tempErrorHolder = new ErrorHolder(); + tempErrorHolder.sErrorType = myParser.getAttributeValue(null, "type"); + if(tempErrorHolder.sParmName != null && TextUtils.isEmpty(tempErrorHolder.sParmName) == false) + { + msErrorString += "Nom: " + tempErrorHolder.sParmName + "\nType: " + tempErrorHolder.sErrorType + "\nDescription: " + tempErrorHolder.sErrorDescription + ")"; + mErrors.add(tempErrorHolder); + tempErrorHolder = null; + } + } + else if (name.equals("parm-error")) + { + if(tempErrorHolder == null) + tempErrorHolder = new ErrorHolder(); + tempErrorHolder.sParmName = myParser.getAttributeValue(null, "name"); + tempErrorHolder.sErrorDescription = myParser.getAttributeValue(null, "desc"); + if(tempErrorHolder.sErrorType != null && TextUtils.isEmpty(tempErrorHolder.sErrorType) == false) + { + msErrorString += "Nom: " + tempErrorHolder.sParmName + "\nType: " + tempErrorHolder.sErrorType + "\nDescription: " + tempErrorHolder.sErrorDescription + ")"; + mErrors.add(tempErrorHolder); + tempErrorHolder = null; + } + } + break; + case XmlPullParser.END_TAG: + //logMessage("XML Element:", EMessageType.VERBOSE); + break; + } + event = myParser.next(); + } + + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void onEMDKManagerClosed() + { + cleanUp(); + } + + private void cleanUp() + { + if(mProfileManager != null) + { + mProfileManager = null; + logMessage("Profile Manager reseted.", EMessageType.DEBUG); + } + + //This callback will be issued when the EMDK closes unexpectedly. + if (mEMDKManager != null) { + mEMDKManager.release(); + logMessage("EMDKManager released.", EMessageType.DEBUG); + mEMDKManager = null; + logMessage("EMDKManager reseted.", EMessageType.DEBUG); + } + } + + + private void logMessage(String message, EMessageType messageType) + { + switch(messageType) + { + case ERROR: + Log.e(TAG, message); + onProfileExecutedStatusChanged("ERROR:" + message); + break; + case SUCCESS: + Log.v(TAG, message); + onProfileExecutedStatusChanged("SUCCESS:" + message); + break; + case VERBOSE: + Log.v(TAG, message); + onProfileExecutedStatusChanged("VERBOSE:" + message); + break; + case WARNING: + Log.w(TAG, message); + onProfileExecutedStatusChanged("WARNING:" + message); + break; + case DEBUG: + Log.d(TAG,message); + onProfileExecutedStatusChanged("DEBUG:" + message); + } + } + + private String getResultCode(EMDKResults.STATUS_CODE aStatusCode) + { + switch (aStatusCode) + { + case FAILURE: + return "FAILURE"; + case NULL_POINTER: + return "NULL_POINTER"; + case EMPTY_PROFILENAME: + return "EMPTY_PROFILENAME"; + case EMDK_NOT_OPENED: + return "EMDK_NOT_OPENED"; + case CHECK_XML: + return "CHECK_XML"; + case PREVIOUS_REQUEST_IN_PROGRESS: + return "PREVIOUS_REQUEST_IN_PROGRESS"; + case PROCESSING: + return "PROCESSING"; + case NO_DATA_LISTENER: + return "NO_DATA_LISTENER"; + case FEATURE_NOT_READY_TO_USE: + return "FEATURE_NOT_READY_TO_USE"; + case FEATURE_NOT_SUPPORTED: + return "FEATURE_NOT_SUPPORTED"; + case UNKNOWN: + default: + return "UNKNOWN"; + } + } +} diff --git a/hsdemo/src/main/java/com/zebra/hsdemo/MainActivity.java b/hsdemo/src/main/java/com/zebra/hsdemo/MainActivity.java index dfd99d4..18d11b3 100644 --- a/hsdemo/src/main/java/com/zebra/hsdemo/MainActivity.java +++ b/hsdemo/src/main/java/com/zebra/hsdemo/MainActivity.java @@ -89,6 +89,7 @@ public class MainActivity extends AppCompatActivity { private List devices; private boolean targetBt = false; + private EMDKUtils emdkUtils = null; AudioRecord recorder = null; Thread recordingThread = null; @@ -191,10 +192,24 @@ public void onClick(View view) { stopRecording(); } }); + + findViewById(R.id.btPlayWithMPOld).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + playWithMediaPlayerOld(); + } + }); + findViewById(R.id.btPlayWithMP).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - playWithMediaPlayer(); + new Thread(new Runnable() { + @Override + public void run() { + // Your code here + playWithMediaPlayer(); + } + }).start(); } }); @@ -212,15 +227,13 @@ public void run() { } }); - - /*findViewById(R.id.btPlayWithATMG).setOnClickListener(new View.OnClickListener() - { + findViewById(R.id.btActivateVolumeProfile).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - playPcmFileWithAudioTrack(true); + activateMaximumAudioProfile(); } }); - */ + setButtonVisibility(true); TextView tvRecordingGain = findViewById(R.id.tvRecordingGain); @@ -341,7 +354,7 @@ private void startRecording(){ audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION); - bufSize = AudioRecord.getMinBufferSize(sampleRate, channelInConfig, audioFormat)*10; + bufSize = AudioRecord.getMinBufferSize(sampleRate, channelInConfig, audioFormat); try { recorder = new AudioRecord( MediaRecorder.AudioSource.MIC , sampleRate, channelInConfig, audioFormat, bufSize); @@ -508,6 +521,54 @@ public void onCompletion(MediaPlayer mp) { } } + private void playWithMediaPlayerOld() + { + File recordedFile = new File(getFilename()); + if(recordedFile.exists()) + { + if (isHeadsetConnected()) { + startBluetoothSCOAudio(true); + } + + Uri fileAsUri = null; + try { + fileAsUri = MediaFileUtils.encodePCMtoWavThenTransferFileToMediaStore(this, recordedFile, sampleRate, channelNumber, bitDepth, replayGain); + } catch (IOException e) { + Log.e(TAG, "Exception: " + e); + e.printStackTrace(); + return; + } + + MediaPlayer mediaPlayer = new MediaPlayer(); + + mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { + @Override + public void onCompletion(MediaPlayer mp) { + mp.stop(); + mp.release(); + if (audioManager.isBluetoothScoOn()) { + Log.w(TAG, "Stop play Disconnect BTSCO play"); + startBluetoothSCOAudio(false); + } else + Log.w(TAG, "play BTSCO is not connected"); + + } + }); + try { + mediaPlayer.setDataSource(this, fileAsUri); + mediaPlayer.setAudioStreamType(AudioManager.STREAM_VOICE_CALL); + mediaPlayer.prepare(); + mediaPlayer.start(); + } catch (Exception e) { + e.printStackTrace(); + } + } + else + { + Toast.makeText(this, "No recorded data found.", Toast.LENGTH_SHORT).show(); + } + } + private boolean isHeadsetConnected() { boolean hasConnectedDevice = false; AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS); @@ -583,8 +644,6 @@ private void playPcmFileWithAudioTrack(boolean manualGain) { routeAudioToHeadset(true); } - - AudioAttributes audioAttributes = new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_MEDIA) .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) @@ -635,7 +694,7 @@ private void playPcmFileWithAudioTrack(boolean manualGain) { private void checkIfZebraDeviceToGrantAllPermissions() { - if(Build.MANUFACTURER.toLowerCase().contains("zebra")) { + if(Build.MANUFACTURER.toLowerCase().contains("zebra") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { CriticalPermissionsHelper.grantPermission(this, EPermissionType.ALL_DANGEROUS_PERMISSIONS, new IResultCallbacks() { @Override public void onSuccess(String message, String resultXML) { @@ -720,7 +779,8 @@ private void requestBluetoothPermission() } private void checkAndRequestPermissions() { - String[] permissions = { + + String[] permissions = new String[]{ Manifest.permission.MODIFY_AUDIO_SETTINGS, Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH_CONNECT, @@ -760,8 +820,8 @@ public void onRequestPermissionsResult(int requestCode, String[] permissions, in } if(allgranted == false) { - Toast.makeText(this, "Please accept permissions", Toast.LENGTH_LONG).show(); - checkAndRequestPermissions(); + Toast.makeText(this, "Please accept all permissions", Toast.LENGTH_LONG).show(); + checkIfZebraDeviceToGrantAllPermissions(); } else { @@ -769,4 +829,33 @@ public void onRequestPermissionsResult(int requestCode, String[] permissions, in } } } + + private void activateMaximumAudioProfile() + { + if(emdkUtils == null) { + emdkUtils = new EMDKUtils(this); + emdkUtils.activateVolumeProfile("MAXIMUM", new EMDKUtils.IResultCallbacks() { + @Override + public void onSuccess(String message, String resultXML) { + Toast.makeText(MainActivity.this, "Profile applied with success", Toast.LENGTH_LONG).show(); + emdkUtils = null; + } + + @Override + public void onError(String message, String resultXML) { + Toast.makeText(MainActivity.this, "Error applying profile.\nCheck logcat.", Toast.LENGTH_LONG).show(); + emdkUtils = null; + } + + @Override + public void onDebugStatus(String message) { + + } + }); + } + else + { + Toast.makeText(MainActivity.this, "Activate MAXIMUM audio profile/n is already running", Toast.LENGTH_SHORT).show(); + } + } } diff --git a/hsdemo/src/main/res/layout/activity_main.xml b/hsdemo/src/main/res/layout/activity_main.xml index e511005..2f892ed 100644 --- a/hsdemo/src/main/res/layout/activity_main.xml +++ b/hsdemo/src/main/res/layout/activity_main.xml @@ -1,5 +1,5 @@ - + + +