de.badaix.snapcast.SnapclientService.java Source code

Java tutorial

Introduction

Here is the source code for de.badaix.snapcast.SnapclientService.java

Source

/*
 *     This file is part of snapcast
 *     Copyright (C) 2014-2016  Johannes Pohl
 *
 *     This program is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package de.badaix.snapcast;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.net.wifi.WifiManager;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;

import static android.os.PowerManager.PARTIAL_WAKE_LOCK;

/**
 * Created by johannes on 01.01.16.
 */

public class SnapclientService extends Service {

    public static final String EXTRA_HOST = "EXTRA_HOST";
    public static final String EXTRA_PORT = "EXTRA_PORT";
    public static final String ACTION_START = "ACTION_START";
    public static final String ACTION_STOP = "ACTION_STOP";
    private final IBinder mBinder = new LocalBinder();
    private java.lang.Process process = null;
    private PowerManager.WakeLock wakeLock = null;
    private WifiManager.WifiLock wifiWakeLock = null;
    private Thread reader = null;
    private boolean running = false;
    private SnapclientListener listener = null;
    private boolean logReceived;

    public boolean isRunning() {
        return running;
    }

    public void setListener(SnapclientListener listener) {
        this.listener = listener;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (intent == null)
            return START_NOT_STICKY;

        if (intent.getAction() == ACTION_STOP) {
            stopService();
            return START_NOT_STICKY;
        } else if (intent.getAction() == ACTION_START) {
            String host = intent.getStringExtra(EXTRA_HOST);
            int port = intent.getIntExtra(EXTRA_PORT, 1704);

            Intent stopIntent = new Intent(this, SnapclientService.class);
            stopIntent.setAction(ACTION_STOP);
            PendingIntent piStop = PendingIntent.getService(this, 0, stopIntent, 0);

            NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                    .setSmallIcon(R.drawable.ic_media_play).setTicker(getText(R.string.ticker_text))
                    .setContentTitle(getText(R.string.notification_title))
                    .setContentText(getText(R.string.notification_text)).setContentInfo(host)
                    .setStyle(new NotificationCompat.BigTextStyle().bigText(getText(R.string.notification_text)))
                    .addAction(R.drawable.ic_media_stop, getString(R.string.stop), piStop);

            Intent resultIntent = new Intent(this, MainActivity.class);

            // The stack builder object will contain an artificial back stack for the
            // started Activity.
            // This ensures that navigating backward from the Activity leads out of
            // your application to the Home screen.
            TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
            // Adds the back stack for the Intent (but not the Intent itself)
            stackBuilder.addParentStack(MainActivity.class);
            // Adds the Intent that starts the Activity to the top of the stack
            stackBuilder.addNextIntent(resultIntent);
            PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
            builder.setContentIntent(resultPendingIntent);
            NotificationManager mNotificationManager = (NotificationManager) getSystemService(
                    Context.NOTIFICATION_SERVICE);
            // mId allows you to update the notification later on.
            final Notification notification = builder.build();
            //        mNotificationManager.notify(123, notification);
            startForeground(123, notification);

            start(host, port);
            return START_STICKY;
        }
        return START_NOT_STICKY;
    }

    @Override
    public void onDestroy() {
        stop();
    }

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

    public void stopPlayer() {
        stopService();
    }

    private void stopService() {
        stop();
        stopForeground(true);
        NotificationManager mNotificationManager = (NotificationManager) getSystemService(
                Context.NOTIFICATION_SERVICE);
        mNotificationManager.cancel(123);
    }

    private void start(String host, int port) {
        try {
            //https://code.google.com/p/android/issues/detail?id=22763
            if (running)
                return;
            File binary = new File(getFilesDir(), "snapclient");
            android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);

            PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
            wakeLock = powerManager.newWakeLock(PARTIAL_WAKE_LOCK, "SnapcastWakeLock");
            wakeLock.acquire();

            WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
            wifiWakeLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "SnapcastWifiWakeLock");
            wifiWakeLock.acquire();

            process = new ProcessBuilder()
                    .command(binary.getAbsolutePath(), "-h", host, "-p", Integer.toString(port))
                    .redirectErrorStream(true).start();

            Thread reader = new Thread(new Runnable() {
                @Override
                public void run() {
                    BufferedReader bufferedReader = new BufferedReader(
                            new InputStreamReader(process.getInputStream()));
                    String line;
                    try {
                        while ((line = bufferedReader.readLine()) != null) {
                            log(line);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });
            logReceived = false;
            reader.start();

            //TODO: wait for started message on stdout
            /*            long now = System.currentTimeMillis();
                        while (!logReceived) {
            if (System.currentTimeMillis() > now + 1000)
                throw new Exception("start timeout");
            Thread.sleep(100, 0);
                        }
            */
        } catch (Exception e) {
            e.printStackTrace();
            if (listener != null)
                listener.onError(this, e.getMessage(), e);
            stop();
        }
    }

    private void log(String msg) {
        if (!logReceived) {
            logReceived = true;
            running = true;
            if (listener != null)
                listener.onPlayerStart(this);
        }
        if (listener != null) {
            int idxBracketOpen = msg.indexOf('[');
            int idxBracketClose = msg.indexOf(']', idxBracketOpen);
            if ((idxBracketOpen > 0) && (idxBracketClose > 0)) {
                listener.onLog(SnapclientService.this, msg.substring(0, idxBracketOpen - 1),
                        msg.substring(idxBracketOpen + 1, idxBracketClose), msg.substring(idxBracketClose + 2));
            }
        }
    }

    private void stop() {
        try {
            if (reader != null)
                reader.interrupt();
            if (process != null)
                process.destroy();
            if ((wakeLock != null) && wakeLock.isHeld())
                wakeLock.release();
            if ((wifiWakeLock != null) && wifiWakeLock.isHeld())
                wifiWakeLock.release();
            android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_DEFAULT);
            running = false;
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (listener != null)
            listener.onPlayerStop(this);
    }

    public interface SnapclientListener {
        void onPlayerStart(SnapclientService snapclientService);

        void onPlayerStop(SnapclientService snapclientService);

        void onLog(SnapclientService snapclientService, String timestamp, String logClass, String msg);

        void onError(SnapclientService snapclientService, String msg, Exception exception);
    }

    /**
     * Class used for the client Binder.  Because we know this service always
     * runs in the same process as its clients, we don't need to deal with IPC.
     */
    public class LocalBinder extends Binder {
        SnapclientService getService() {
            // Return this instance of LocalService so clients can call public methods
            return SnapclientService.this;
        }
    }

    // Handler that receives messages from the thread
    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            // Normally we would do some work here, like download a file.
            // For our sample, we just sleep for 5 seconds.
            long endTime = System.currentTimeMillis() + 5 * 1000;
            while (System.currentTimeMillis() < endTime) {
                synchronized (this) {
                    try {
                        wait(endTime - System.currentTimeMillis());
                    } catch (Exception e) {
                    }
                }
            }
            // Stop the service using the startId, so that we don't stop
            // the service in the middle of handling another job
            stopSelf(msg.arg1);
        }
    }

}