1

I am not able to record audio of both side in android. only one side audio is recording using below code. is both side audio recording is possible in android?

public class CallRecordingService extends Service {

private MediaPlayer mPlayer = null;
private MediaRecorder mRecorder = null;
private static final String LOG_TAG = CallRecordingService.class.getSimpleName();
private String savingPath;
boolean isRecordStarted = false;

@Nullable
@Override
public IBinder onBind(Intent intent) {
    return null;
}

@Override
public void onCreate() {
    super.onCreate();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    super.onStartCommand(intent, flags, startId);
    savingPath = intent.getStringExtra("OUTPUT_PATH");
    AppApplication.getInstance().saveIntoPrefs(MConstants.RECORDING_FILE_PATH, savingPath);
    startRecording();
    return START_NOT_STICKY;
}

// this process must be done prior to the start of recording
private void resetRecorder() {
    mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
    mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
        mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
        mRecorder.setAudioEncodingBitRate(48000);
    } else {
        mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
        mRecorder.setAudioEncodingBitRate(64000);
    }
    mRecorder.setAudioSamplingRate(8000);
    mRecorder.setOutputFile(savingPath);

    try {
        mRecorder.prepare();
    } catch (IllegalStateException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

private void startRecording() {
    if (mRecorder != null) {
        mRecorder.stop();
        mRecorder.release();
    }
    mRecorder = new MediaRecorder();
    resetRecorder();
    try {
        // Sometimes prepare takes some time to complete
        Thread.sleep(2000);
        if (!isRecordStarted) {
            mRecorder.start();
            isRecordStarted = true;
        } else if (isRecordStarted) {
            isRecordStarted = false;
            stopRecording();
        }
    } catch (InterruptedException | IllegalStateException e) {
        e.printStackTrace();
    }
}

public void stopRecording() {
    if (mRecorder != null) {
        mRecorder.stop();
        mRecorder.reset();
        mRecorder.release();
        mRecorder = null;
    }
}

}

I am stuck to implement this recording feature in my application. If anyone has completed such kind of implementation then plz address my mistake.

  • Which is "the other side"? Do you mean During a Voicecall? If Yes: on newer (from 7 to 8.1) Android versions recording both sides are disabled and the success depends of the manufacturer implementation of it. There are few methods for Android 7 but less for 7.1 and more less for 8.x. Those undocumented methods are quite well kept reserved from App developers due to the hard work spent to found these methods. – emandt Oct 20 '18 at 08:26
  • He is right. Don't bother with it see [here](https://www.androidpolice.com/2018/08/15/android-9-pie-puts-end-third-party-call-recording-apps/) – Itamar Kerbel Oct 20 '18 at 09:45
  • Thank you emandt. Yes, I mean during Voicecall. So I can't implement call recording in my app. if anyone have done this feature then plz suggest me what i need to implement. – user1557803 Oct 22 '18 at 08:33

2 Answers2

0

AndroidManifest.xml

<manifest>
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    ....
    <application>
    ....
    <service
        android:name=".CallForegroundService"
        android:enabled="true"
        android:exported="false" />
    </application>
</manifest>

ForegroundService

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;
import android.media.MediaRecorder;
import android.os.Build;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;

import androidx.annotation.Nullable;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class CallForegroundService extends Service {

    private static final String TAG = "ForegroundService";
    private boolean isStarted;
    private MediaRecorder mRecorder;

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        startRecording(number);
        return START_NOT_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        stopRecording();
    }

    private void startRecording(String number) {
        try {
            String path = getApplicationContext().getFilesDir().getPath();
            //String selectedPath = Environment.getExternalStorageDirectory() + "/Testing";
            //String selectedPath = Environment.getExternalStorageDirectory().getAbsolutePath() +"/Android/data/" + packageName + "/system_sound";

            File file = new File(path);
            if (!file.exists()){
                file.mkdirs();
            }

            mRecorder = new MediaRecorder();
            mRecorder.reset();

            String manufacturer = Build.MANUFACTURER;
            Log.d(TAG, manufacturer);
            /*if (manufacturer.toLowerCase().contains("samsung")) {
                mRecorder.setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION);
            } else {
                mRecorder.setAudioSource(MediaRecorder.AudioSource.VOICE_CALL);
            }*/
            mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); //MIC | VOICE_COMMUNICATION | VOICE_RECOGNITION |
            mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); //THREE_GPP | MPEG_4
            mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); //AMR_NB | AAC
            String mFilePath = file + "/" + "REC_" + number + "_" + new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()) + ".3gp"; //.3gp | .mp3
            mRecorder.setOutputFile(mFilePath);
            mRecorder.prepare();
            mRecorder.start();
            isStarted = true;
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void stopRecording() {
        if (isStarted && mRecorder != null) {
            mRecorder.stop();
            mRecorder.reset(); // You can reuse the object by going back to setAudioSource() step
            mRecorder.release();
            mRecorder = null;
            isStarted = false;
        }
    }
}

MainActivity

public class MainActivity extends AppCompatActivity {
    ArrayList<String> mArrayList;
    MediaPlayer mPlayer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        startService(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        stopService(this);
    }

    private void startService(Context context) {
        ContextCompat.startForegroundService(context, new Intent(context, CallForegroundService.class)); //ForegroundService
    }

    private void stopService(Context context) {
        context.stopService(new Intent(context, CallForegroundService.class)); //ForegroundService
    }

    //RecyclerView.Adapter
    private void getInternalStorageFiles() {
        mArrayList = new ArrayList<>();
        String path = getApplicationContext().getFilesDir().getPath();
        String[] listOfFiles = getApplicationContext().getFilesDir().list();
        Log.d(TAG, "Files: " + new Gson().toJson(listOfFiles));
        if (listOfFiles != null) {
            for (String fileName : listOfFiles) {
                mArrayList.add(fileName+"/"+path));
            }
        }
    }
    private void playRecord(Record model) {
        mPlayer = new MediaPlayer();
        try {
            FileInputStream mInputStream = new FileInputStream(mArrayList.get(i)); //i = for loop
            mPlayer.setDataSource(mInputStream.getFD());
            mInputStream.close();
            mPlayer.prepare();
        } catch (IOException e) {
            e.printStackTrace();
        }
        mPlayer.start();
    }
    private void stopRecord() {
        if (mPlayer != null) {
            mPlayer.stop();
        }
    }
}
Mustofa Kamal
  • 616
  • 7
  • 13
  • How to record audio in background with accessibility service ? – Feroz Siddiqui Apr 12 '20 at 19:17
  • Check it out below the answer – Mustofa Kamal Apr 13 '20 at 20:47
  • Its not working in One plus. I/AudioManager: In setMode(), mode: 2, calling application: D/MyAccessibilityService: hasWiredHeadset: found audio signals over the telephony network D/MyAccessibilityService: OnePlus – Feroz Siddiqui Apr 14 '20 at 03:58
  • https://stackoverflow.com/questions/61180879/android-how-to-record-voip-call-using-foreground-service Please check this. I paste same code in acessibility service but audio is not getting captured or processed. – Feroz Siddiqui Apr 14 '20 at 04:54
  • 1
    I'm testing this code on a Google pixel. It's working well. And your code is not accessibility service. – Mustofa Kamal Apr 14 '20 at 05:17
  • https://stackoverflow.com/questions/61184352/android-accessibility-service-real-time-audio-processing see this post i have posted the entire code – Feroz Siddiqui Apr 14 '20 at 05:43
  • In my phone one plus the file is generated but i cannot listen audio there is no voice – Feroz Siddiqui Apr 14 '20 at 10:20
  • I think you are not testing this while call is connected. I m sure its recording during call. Otherwise its recording audio – Feroz Siddiqui Apr 14 '20 at 11:21
  • @FerozSiddiqui: Your question's title is "MediaRecorder does not record both side audio recordings in android". But you ask me to call record! A call record is another part of logic. This code only for voice record does not call record. – Mustofa Kamal Apr 19 '20 at 14:02
0

Voice Record by Accessibility Service

Gradle

implementation 'pub.devrel:easypermissions:3.0.0'
implementation 'com.google.code.gson:gson:2.8.6'

Manifest.xml

<manifest>
    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>

    <!-- AccessibilityService -->
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

    <application>

        <!-- Accessibility Service -->
        <service android:name=".services.CallAccessibilityService"
            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
            android:label="@string/accessibility_service_label">
            <intent-filter>
                <action android:name="android.accessibilityservice.AccessibilityService" />
            </intent-filter>
            <meta-data
                android:name="android.accessibilityservice"
                android:resource="@xml/accessibility_service_config" />
        </service>
<application>

res > xml > accessibility_service_config

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFeedbackType="feedbackAllMask"
    android:accessibilityFlags="flagDefault"
    android:canRequestEnhancedWebAccessibility="true"
    android:notificationTimeout="100"
    android:packageNames="@null"
    android:canRetrieveWindowContent="true"
    android:canRequestTouchExplorationMode="true"
    />

MainActivity

public class MainActivity extends AppCompatActivity implements  EasyPermissions.PermissionCallbacks {

    //Link: https://developer.android.com/guide/topics/media/mediarecorder?hl=en

    private String[] PERMISSIONS = {
            Manifest.permission.RECORD_AUDIO,
            Manifest.permission.MODIFY_AUDIO_SETTINGS
    };

    private static final int CODE_DRAW_OVER_OTHER_APP_PERMISSION = 2084;
    private static final int RC_APP_PERM = 124;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        // List of Permissions
        requestPermissions();
    }

    @AfterPermissionGranted(RC_APP_PERM)
    private void requestPermissions() {
        if (EasyPermissions.hasPermissions(this, PERMISSIONS)) {
            // Already have permission, do the thing
            onCheckPermissionOverOtherApp();
        } else {
            // Do not have permissions, request them now
            EasyPermissions.requestPermissions(this, "This app needs access to your camera and mic to make video calls", RC_APP_PERM, PERMISSIONS);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
    }

    @Override
    public void onPermissionsGranted(int requestCode, @NonNull List<String> perms) {
        // Some permissions have been granted
        onCheckPermissionOverOtherApp();
        getInternalStorageFiles();
    }

    @Override
    public void onPermissionsDenied(int requestCode, @NonNull List<String> perms) {
        // Some permissions have been denied
    }

    // On over other app | open the settings screen
    private void onCheckPermissionOverOtherApp() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {
            startActivityForResult(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())), CODE_DRAW_OVER_OTHER_APP_PERMISSION);
        } else {
            if (!isAccessibilitySettingsOn(getApplicationContext())) {
                startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS));
            }
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK && requestCode == CODE_DRAW_OVER_OTHER_APP_PERMISSION) {
            if (!isAccessibilitySettingsOn(getApplicationContext())) {
                startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS));
            }
        } else {
            onCheckPermissionOverOtherApp();
            Toast.makeText(this,"Draw over other app permission not available. Closing the application", Toast.LENGTH_SHORT).show();
        }
    }

    // To check if service is enabled
    public boolean isAccessibilitySettingsOn(Context mContext) {
        int accessibilityEnabled = 0;
        final String service = mContext.getPackageName() + "/" + CallAccessibilityService.class.getCanonicalName();
        try {
            accessibilityEnabled = Settings.Secure.getInt(mContext.getApplicationContext().getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED);
            Log.v(TAG, "accessibilityEnabled = " + accessibilityEnabled);
        } catch (Settings.SettingNotFoundException e) {
            Log.e(TAG, "Error finding setting, default accessibility to not found: " + e.getMessage());
        }
        TextUtils.SimpleStringSplitter mStringColonSplitter = new TextUtils.SimpleStringSplitter(':');

        if (accessibilityEnabled == 1) {
            Log.v(TAG, "***ACCESSIBILITY IS ENABLED*** -----------------");
            String settingValue = Settings.Secure.getString(mContext.getApplicationContext().getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
            if (settingValue != null) {
                mStringColonSplitter.setString(settingValue);
                while (mStringColonSplitter.hasNext()) {
                    String accessibilityService = mStringColonSplitter.next();
                    Log.v(TAG, "-------------- > accessibilityService :: " + accessibilityService + " " + service);
                    if (accessibilityService.equalsIgnoreCase(service)) {
                        Log.v(TAG, "We've found the correct setting - accessibility is switched on!");
                        return true;
                    }
                }
            }
        } else {
            Log.v(TAG, "***ACCESSIBILITY IS DISABLED***");
        }
        return false;
    }


    private void getInternalStorageFiles() {
        //String path = Environment.getExternalStorageDirectory().toString() + "/Testing"; //getExternalFilesDir(), getExternalCacheDir(), or getExternalMediaDir()
        //String path = this.getApplicationContext().getFilesDir() + "/system_sound"; //file.getAbsolutePath()
        //String[] listOfFiles = Environment.getExternalStoragePublicDirectory (Environment.DIRECTORY_DOWNLOADS).list();

        String path = getApplicationContext().getFilesDir().getPath();
        String[] listOfFiles = getApplicationContext().getFilesDir().list();
        Log.d(TAG, "Files: " + new Gson().toJson(listOfFiles));
        if (listOfFiles != null) {
            for (String fileName : listOfFiles) {
                Log.d(TAG, "" + fileName +" | "+ path));
            }
        }
    }

}

layout > action_bar.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <!--Root container-->
    <LinearLayout
        android:id="@+id/root_container"
        android:gravity="center"
        android:padding="10dp"
        android:orientation="vertical"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
        <ImageButton
            android:id="@+id/draggable_button"
            android:layout_width="45dp"
            android:layout_height="45dp"
            android:layout_marginBottom="5dp"
            android:backgroundTint="@color/colorDeepGrey"
            android:background="@drawable/ic_more_horiz_black_24dp" />
        <ImageButton
            android:id="@+id/btnStartRecording"
            android:layout_width="45dp"
            android:layout_height="45dp"
            android:layout_marginBottom="5dp"
            android:background="@drawable/shape_radius_100_stroke_grey_bg_white"
            android:src="@drawable/selector_play_button" />
        <ImageButton
            android:id="@+id/btnStopRecording"
            android:layout_width="45dp"
            android:layout_height="45dp"
            android:layout_marginBottom="5dp"
            android:background="@drawable/shape_radius_100_stroke_grey_bg_white"
            android:src="@drawable/selector_stop_button" />
        <ImageButton
            android:id="@+id/btnClose"
            android:layout_width="45dp"
            android:layout_height="45dp"
            android:background="@drawable/shape_radius_100_stroke_grey_bg_white"
            android:src="@drawable/selector_close_button" />
    </LinearLayout>

</FrameLayout>

CallAccessibilityService

public class CallAccessibilityService extends AccessibilityService {

    private static final String TAG = "MyAccessibilityService";
    private FrameLayout mLayout;
    private boolean isStarted;
    private MediaRecorder mRecorder;

    private View mView;
    private WindowManager mWindowManager;

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {

    }

    @Override
    public void onInterrupt() {
    }

    @Override
    protected void onServiceConnected() {


        displayView();
    }

    private void displayView() {
        //Inflate the floating view layout we created
        mView = LayoutInflater.from(this).inflate(R.layout.action_bar, null);

        //Add the view to the window.
        final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : WindowManager.LayoutParams.TYPE_PHONE,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,      // | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
                PixelFormat.TRANSLUCENT);

        //Specify the view position
        params.gravity = Gravity.TOP | Gravity.LEFT;        //Initially view will be added to top-left corner | Gravity.CENTER_VERTICAL|Gravity.END;
        params.x = 0;
        params.y = 100;

        //Add the view to the window
        mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
        if (mWindowManager != null) {
            mWindowManager.addView(mView, params);
        }

        ((ImageButton) mView.findViewById(R.id.btnClose)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //Open the application  click.
                Intent intent = new Intent(CallAccessibilityService.this, MainActivity.class);
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivity(intent);
                //close the service and remove view from the view hierarchy
                stopSelf();
            }
        });

        ((ImageButton) mView.findViewById(R.id.btnStartRecording)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startRecording();
                Toast.makeText(CallAccessibilityService.this, "Playing", Toast.LENGTH_SHORT).show();
            }
        });
        ((ImageButton) mView.findViewById(R.id.btnStopRecording)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                stopRecording();
                Toast.makeText(CallAccessibilityService.this, "Stopping", Toast.LENGTH_SHORT).show();
            }
        });

        //Drag and move floating view using user's touch action.
        ((ImageButton) mView.findViewById(R.id.draggable_button)).setOnTouchListener(new View.OnTouchListener() {
            private int initialX;
            private int initialY;
            private float initialTouchX;
            private float initialTouchY;

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                //params.x = (int) event.getX();
                //params.y = (int) event.getY();

                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        //remember the initial position.
                        initialX = params.x;
                        initialY = params.y;
                        //get the touch location
                        initialTouchX = event.getRawX();
                        initialTouchY = event.getRawY();
                        return true;
                    case MotionEvent.ACTION_UP:
                        int xDiff = (int) (event.getRawX() - initialTouchX);
                        int yDiff = (int) (event.getRawY() - initialTouchY);
                        //The check for Xdiff <10 && YDiff< 10 because sometime elements moves a little while clicking.
                        //So that is click event.
                        if (xDiff < 10 && yDiff < 10) {
                            Toast.makeText(CallAccessibilityService.this, "ACTION_UP", Toast.LENGTH_SHORT).show();
                        }
                        return true;
                    case MotionEvent.ACTION_MOVE:
                        //Calculate the X and Y coordinates of the view.
                        params.x = initialX + (int) (event.getRawX() - initialTouchX);
                        params.y = initialY + (int) (event.getRawY() - initialTouchY);
                        //Update the layout with new X & Y coordinate
                        mWindowManager.updateViewLayout(mView, params);
                        return true;
                }
                return false;
            }
        });
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mView != null) mWindowManager.removeView(mView);
    }

    public void startRecording() {
        try {
            String path = getApplicationContext().getFilesDir().getPath();
            //String selectedPath = Environment.getExternalStorageDirectory() + "/Testing";
            //String selectedPath = Environment.getExternalStorageDirectory().getAbsolutePath() +"/Android/data/" + packageName + "/system_sound";

            File file = new File(path);
            if (!file.exists()){
                file.mkdirs();
            }

            mRecorder = new MediaRecorder();
            mRecorder.reset();


            //android.permission.RECORD_AUDIO
            String manufacturer = Build.MANUFACTURER;
            Log.d(TAG, manufacturer);
            mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); //MIC | VOICE_COMMUNICATION (Android 10 release) | VOICE_RECOGNITION | (VOICE_CALL = VOICE_UPLINK + VOICE_DOWNLINK)
            mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); //THREE_GPP | MPEG_4
            mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); //AMR_NB | AAC


            String mFilePath = file + "/" + "REC_" + new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()) + ".3gp"; //.3gp | .mp3
            mRecorder.setOutputFile(mFilePath);
            mRecorder.prepare();
            mRecorder.start();
            isStarted = true;
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void stopRecording() {
        if (isStarted && mRecorder != null) {
            mRecorder.stop();
            mRecorder.reset(); // You can reuse the object by going back to setAudioSource() step
            mRecorder.release();
            mRecorder = null;
            isStarted = false;
        }
    }


    }


}
Mustofa Kamal
  • 616
  • 7
  • 13