Building an Oscam APK for Android

Hello again.
Here is a tutorial on how to create an Oscam APK for your Android device.
On the previous tutorial, I showed you how to compile a native Oscam binary that is able to run on Android and I promised I would make another tutorial on how to build an APK with this binary and run it.

Lets get started.
You will need to download Android Studio from https://developer.android.com/studio/index.html

For this tutorial, it does not matter which OS your are running on as long as you have access to the compiled Oscam binary ready to include on your APK.

All the code below is also available on GitHub, so if you want to skip the tutorial, just clone it and get going…

Create a new Android Project with an Empty Activity.

Now create a “raw” folder under the “res” folder.

Grab the oscam binary and place it under the raw folder.

 

Now, lets add some widgets to the user interface and make some code.

They XML code for the above layout should be something like this :

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.tekreaders.myoscamapk.MainActivity">
 
    <Button
        android:id="@+id/startButton"
        android:layout_alignParentTop="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Start Oscam"
        android:onClick="startOscam"
        />
    <Button
        android:id="@+id/stopButton"
        android:layout_toRightOf="@+id/startButton"
        android:layout_alignParentTop="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Stop Oscam"
        android:onClick="stopOscam"
        />
 
    <TextView
        android:id="@+id/outputView"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentLeft="true"
        android:layout_below="@+id/startButton"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:maxLines="200"
        android:scrollbars="vertical"
        android:text="Oscam log will be displayed here..."
         />
</RelativeLayout>

Now for the code for the entire MainActivity :

public class MainActivity extends AppCompatActivity {
    TextView outputView;
    Button startButton;
    Button stopButton;
 
    private String confDir = Environment.getExternalStorageDirectory().getPath() + "/oscam";
    private String tmpDir = Environment.getExternalStorageDirectory().getPath() + "/oscam/tmp";
    private String oscamFilename = "";
    private Process oscamProcess;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        outputView = (TextView)findViewById(R.id.outputView);
        startButton = (Button)findViewById(R.id.startButton);
        stopButton = (Button)findViewById(R.id.stopButton);
        outputView.setMovementMethod(new ScrollingMovementMethod());
        requestPermissions();
    }
 
    //request the runtime permissions, this required since Marshmallow
    public void requestPermissions() {
        if (Build.VERSION.SDK_INT >= 23) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
        }
    }
 
    //output logs and enable/disable buttons according to the running state
    public void setStatus(final boolean running, final String message){
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if (message!=null) {
                    outputView.setText(outputView.getText()+"\n"+message);
                    if (outputView.getVisibility() == View.VISIBLE) {
                        final int scrollAmount = outputView.getLayout().getLineTop(outputView.getLineCount()) - outputView.getHeight();
                        if (scrollAmount > 0)
                            outputView.scrollTo(0, scrollAmount);
                        else
                            outputView.scrollTo(0, 0);
                    }
                }
                startButton.setEnabled(!running);
                stopButton.setEnabled(running);
            }
        });
    }
 
    public void startOscam(View view){
        if (oscamProcess == null) {
            //run this process on a new thread to avoid UI blocking
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        setStatus(true, "Initializing...");
                        //check the config and tmp folders
                        File cdir = new File(confDir);
                        File tdir = new File(tmpDir);
                        boolean confDirExists = cdir.exists();
                        boolean tmpDirExists = tdir.exists();
                        if (!confDirExists)
                            confDirExists = cdir.mkdir();
                        if (!tmpDirExists)
                            tmpDirExists = tdir.mkdir();
                        if (confDirExists && tmpDirExists) {
                            //check the stat file to sanitize the default errors
                            File stat = new File(tmpDir + "/stat");
                            if (!stat.exists()) {
                                FileWriter statfile = new FileWriter(stat);
                                statfile.write("");
                                statfile.close();
                            }
                            //extract the resource in the raw folder into the App private space and overwrite if it already exists
                            String appFileDirectory = getFilesDir().getParent();
                            oscamFilename = appFileDirectory + "/oscam";
                            InputStream ins = getResources().openRawResource(R.raw.oscam);
                            final byte[] buffer = new byte[ins.available()];
                            ins.read(buffer);
                            ins.close();
                            File destination = new File(oscamFilename);
                            if (destination.exists())
                                destination.delete();
                            FileOutputStream fos = new FileOutputStream(destination);
                            fos.write(buffer);
                            fos.close();
                            if (destination.exists()) {
                                //set executable rights on the file (chmod)
                                boolean isExecutable = destination.setExecutable(true);
                                if (isExecutable) {
                                    setStatus(true, "Launching oscam with confDir='" + confDir + "' and tmpDir='" + tmpDir + "'...");
                                    //start the Oscam process
                                    oscamProcess = new ProcessBuilder().command(oscamFilename, "--config-dir", confDir, "--temp-dir", tmpDir).redirectErrorStream(true).start();
                                    //start a thread to read Oscam logs and print them on our textview
                                    Thread readThread = new Thread(new Runnable() {
                                        @Override
                                        public void run() {
                                            try {
                                                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(oscamProcess.getInputStream()));
                                                while (!Thread.currentThread().isInterrupted()) {
                                                    if (bufferedReader.ready()) {
                                                        String line = bufferedReader.readLine();
                                                        setStatus(true, line);
                                                    }
                                                }
                                                bufferedReader.close();
                                            } catch (Exception e) {
                                                e.printStackTrace();
                                            }
                                        }
                                    });
                                    readThread.start();
                                    //wait for the process to terminate
                                    oscamProcess.waitFor();
                                    //when we get here, the process is already dead, we should cleanup
                                    readThread.interrupt();
                                    oscamProcess = null;
                                    setStatus(false, "Oscam stopped!");
                                } else
                                    setStatus(false, "Oscam is not executable!");
                            } else
                                setStatus(false, "Oscam binary not found!");
                        } else
                            setStatus(false, "Unable to read/write configuration and tmp folder!");
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
 
    public void stopOscam(View view){
        if (oscamProcess != null) {
            try {
                //get the Oscam process PID by using reflection
                long pid = -1;
                try {
                    Field f = oscamProcess.getClass().getDeclaredField("pid");
                    f.setAccessible(true);
                    pid = f.getLong(oscamProcess);
                    f.setAccessible(false);
                } catch (Exception e) {
                    pid = -1;
                }
                //send a SIGTERM to the oscam process
                Runtime.getRuntime().exec("kill -15 " + pid);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }else
            setStatus(false, "Oscam stopped!");
    }
 
 
}

 

If you run the app and click the start button you should see something like this.

 

And that is it. You just made an Oscam APK that you can start and stop. Remember to place your configuration files in the /sdcard/oscam/ folder and you are ready to go.

The sample project with all the code above is also available on GitHub.

There is obviously more you can do to improve this app. For example, this should be a Service and run on boot and never be killed by the system.
But that, is out of the scope of this tutorial and you should look into the Android Documentation to learn more.

 

🙂

This entry was posted in Android, Java, Oscam, Programming.

Leave a Reply

Your email address will not be published. Required fields are marked *

Please calculate the arithmetic expression to validate you are human. *