de.badaix.snapcast.MainActivity.java Source code

Java tutorial

Introduction

Here is the source code for de.badaix.snapcast.MainActivity.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.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.net.nsd.NsdServiceInfo;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.Snackbar;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;

import org.json.JSONException;
import org.json.JSONObject;

import java.net.UnknownHostException;
import java.util.Vector;

import de.badaix.snapcast.control.RemoteControl;
import de.badaix.snapcast.control.json.Client;
import de.badaix.snapcast.control.json.ServerStatus;
import de.badaix.snapcast.control.json.Stream;
import de.badaix.snapcast.utils.NsdHelper;
import de.badaix.snapcast.utils.Settings;
import de.badaix.snapcast.utils.Setup;

public class MainActivity extends AppCompatActivity implements ClientItem.ClientInfoItemListener,
        RemoteControl.RemoteControlListener, SnapclientService.SnapclientListener, NsdHelper.NsdHelperListener {

    static final int CLIENT_PROPERTIES_REQUEST = 1;
    private static final String TAG = "Main";
    private static final String SERVICE_NAME = "Snapcast";// #2";
    boolean bound = false;
    private MenuItem miStartStop = null;
    private MenuItem miSettings = null;
    //    private MenuItem miRefresh = null;
    private String host = "";
    private int port = 1704;
    private int controlPort = 1705;
    private RemoteControl remoteControl = null;
    private ServerStatus serverStatus = null;
    private SnapclientService snapclientService;
    private SectionsPagerAdapter sectionsPagerAdapter;
    private TabLayout tabLayout;
    private Snackbar warningSamplerateSnackbar = null;
    private int nativeSampleRate = 0;
    private CoordinatorLayout coordinatorLayout;

    /**
     * The {@link ViewPager} that will host the section contents.
     */
    private ViewPager mViewPager;

    /**
     * Defines callbacks for service binding, passed to bindService()
     */
    private ServiceConnection mConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName className, IBinder service) {
            // We've bound to LocalService, cast the IBinder and get LocalService instance
            SnapclientService.LocalBinder binder = (SnapclientService.LocalBinder) service;
            snapclientService = binder.getService();
            snapclientService.setListener(MainActivity.this);
            bound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            bound = false;
        }
    };

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

        for (int rate : new int[] { 8000, 11025, 16000, 22050, 44100, 48000 }) { // add the rates you wish to check against
            Log.d(TAG, "Samplerate: " + rate);
            int bufferSize = AudioRecord.getMinBufferSize(rate, AudioFormat.CHANNEL_OUT_STEREO,
                    AudioFormat.ENCODING_PCM_16BIT);
            if (bufferSize > 0) {
                Log.d(TAG, "Samplerate: " + rate + ", buffer: " + bufferSize);
            }
        }

        AudioManager audioManager = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            String rate = audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
            nativeSampleRate = Integer.valueOf(rate);
            //            String size = audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
            //            tvInfo.setText("Sample rate: " + rate + ", buffer size: " + size);
        }

        coordinatorLayout = (CoordinatorLayout) findViewById(R.id.myCoordinatorLayout);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        // Create the adapter that will return a fragment for each of the three
        // primary sections of the activity.
        sectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());

        // Set up the ViewPager with the sections adapter.
        mViewPager = (ViewPager) findViewById(R.id.container);
        mViewPager.setAdapter(sectionsPagerAdapter);

        tabLayout = (TabLayout) findViewById(R.id.tabs);
        tabLayout.setupWithViewPager(mViewPager);
        mViewPager.setVisibility(View.GONE);

        setActionbarSubtitle("Host: no Snapserver found");

        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "copying snapclient");
                Setup.copyBinAsset(MainActivity.this, "snapclient", "snapclient");
                Log.d(TAG, "done copying snapclient");
            }
        }).start();

        sectionsPagerAdapter.setHideOffline(Settings.getInstance(this).getBoolean("hide_offline", false));
    }

    public void checkFirstRun() {
        PackageInfo pInfo = null;
        try {
            pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
            final int verCode = pInfo.versionCode;
            int lastRunVersion = Settings.getInstance(this).getInt("lastRunVersion", 0);
            Log.d(TAG, "lastRunVersion: " + lastRunVersion + ", version: " + verCode);
            if (lastRunVersion < verCode) {
                // Place your dialog code here to display the dialog
                new AlertDialog.Builder(this).setTitle(R.string.first_run_title).setMessage(R.string.first_run_text)
                        .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                Settings.getInstance(MainActivity.this).put("lastRunVersion", verCode);
                            }
                        }).setCancelable(true).show();
            }
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_snapcast, menu);
        miStartStop = menu.findItem(R.id.action_play_stop);
        miSettings = menu.findItem(R.id.action_settings);
        //        miRefresh = menu.findItem(R.id.action_refresh);
        updateStartStopMenuItem();
        boolean isChecked = Settings.getInstance(this).getBoolean("hide_offline", false);
        MenuItem menuItem = menu.findItem(R.id.action_hide_offline);
        menuItem.setChecked(isChecked);
        sectionsPagerAdapter.setHideOffline(isChecked);
        //        setHost(host, port, controlPort);
        if (remoteControl != null) {
            updateMenuItems(remoteControl.isConnected());
        }
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            ServerDialogFragment serverDialogFragment = new ServerDialogFragment();
            serverDialogFragment.setHost(Settings.getInstance(this).getHost(),
                    Settings.getInstance(this).getStreamPort(), Settings.getInstance(this).getControlPort());
            serverDialogFragment.setAutoStart(Settings.getInstance(this).isAutostart());
            serverDialogFragment.setListener(new ServerDialogFragment.ServerDialogListener() {
                @Override
                public void onHostChanged(String host, int streamPort, int controlPort) {
                    setHost(host, streamPort, controlPort);
                    startRemoteControl();
                }

                @Override
                public void onAutoStartChanged(boolean autoStart) {
                    Settings.getInstance(MainActivity.this).setAutostart(autoStart);
                }
            });
            serverDialogFragment.show(getSupportFragmentManager(), "serverDialogFragment");
            //            NsdHelper.getInstance(this).startListening("_snapcast._tcp.", SERVICE_NAME, this);
            return true;
        } else if (id == R.id.action_play_stop) {
            if (bound && snapclientService.isRunning()) {
                stopSnapclient();
            } else {
                item.setEnabled(false);
                startSnapclient();
            }
            return true;
        } else if (id == R.id.action_hide_offline) {
            item.setChecked(!item.isChecked());
            Settings.getInstance(this).put("hide_offline", item.isChecked());
            sectionsPagerAdapter.setHideOffline(item.isChecked());
            return true;
        } else if (id == R.id.action_refresh) {
            startRemoteControl();
            remoteControl.getServerStatus();
        } else if (id == R.id.action_about) {
            Intent intent = new Intent(this, AboutActivity.class);
            startActivity(intent);
        }

        return super.onOptionsItemSelected(item);
    }

    private void updateStartStopMenuItem() {
        MainActivity.this.runOnUiThread(new Runnable() {
            @Override
            public void run() {

                if (bound && snapclientService.isRunning()) {
                    Log.d(TAG, "updateStartStopMenuItem: ic_media_stop");
                    miStartStop.setIcon(R.drawable.ic_media_stop);
                } else {
                    Log.d(TAG, "updateStartStopMenuItem: ic_media_play");
                    miStartStop.setIcon(R.drawable.ic_media_play);
                }
                if (miStartStop != null) {
                    miStartStop.setEnabled(true);
                }
            }
        });
    }

    private void startSnapclient() {
        if (TextUtils.isEmpty(host))
            return;

        Intent i = new Intent(this, SnapclientService.class);
        i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
        i.putExtra(SnapclientService.EXTRA_HOST, host);
        i.putExtra(SnapclientService.EXTRA_PORT, port);
        i.setAction(SnapclientService.ACTION_START);

        startService(i);
    }

    private void stopSnapclient() {
        if (bound)
            snapclientService.stopPlayer();
        //        stopService(new Intent(this, SnapclientService.class));
        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    }

    private void startRemoteControl() {
        if (remoteControl == null)
            remoteControl = new RemoteControl(this);
        if (!host.isEmpty())
            remoteControl.connect(host, controlPort);
    }

    private void stopRemoteControl() {
        if ((remoteControl != null) && (remoteControl.isConnected()))
            remoteControl.disconnect();
        remoteControl = null;
    }

    @Override
    public void onResume() {
        super.onResume();
        startRemoteControl();
        checkFirstRun();
    }

    @Override
    public void onStart() {
        super.onStart();

        if (TextUtils.isEmpty(Settings.getInstance(this).getHost()))
            NsdHelper.getInstance(this).startListening("_snapcast._tcp.", SERVICE_NAME, this);
        else
            setHost(Settings.getInstance(this).getHost(), Settings.getInstance(this).getStreamPort(),
                    Settings.getInstance(this).getControlPort());

        Intent intent = new Intent(this, SnapclientService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

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

    @Override
    public void onStop() {
        super.onStop();

        NsdHelper.getInstance(this).stopListening();
        // Unbind from the service
        if (bound) {
            unbindService(mConnection);
            bound = false;
        }
    }

    @Override
    public void onPlayerStart(SnapclientService snapclientService) {
        Log.d(TAG, "onPlayerStart");
        updateStartStopMenuItem();
    }

    @Override
    public void onPlayerStop(SnapclientService snapclientService) {
        Log.d(TAG, "onPlayerStop");
        updateStartStopMenuItem();
        if (warningSamplerateSnackbar != null)
            warningSamplerateSnackbar.dismiss();
    }

    @Override
    public void onLog(SnapclientService snapclientService, String timestamp, String logClass, String msg) {
        Log.d(TAG, "[" + logClass + "] " + msg);
        if ("state".equals(logClass)) {
            if (msg.startsWith("sampleformat")) {
                msg = msg.substring(msg.indexOf(":") + 2);
                Log.d(TAG, "sampleformat: " + msg);
                if (msg.indexOf(':') > 0) {
                    int samplerate = Integer.valueOf(msg.substring(0, msg.indexOf(':')));

                    if (warningSamplerateSnackbar != null)
                        warningSamplerateSnackbar.dismiss();

                    if ((nativeSampleRate != 0) && (nativeSampleRate != samplerate)) {
                        warningSamplerateSnackbar = Snackbar.make(coordinatorLayout,
                                getString(R.string.wrong_sample_rate, samplerate, nativeSampleRate),
                                Snackbar.LENGTH_INDEFINITE);
                        warningSamplerateSnackbar.show();
                    } else if (nativeSampleRate == 0) {
                        warningSamplerateSnackbar = Snackbar.make(coordinatorLayout,
                                getString(R.string.unknown_sample_rate), Snackbar.LENGTH_LONG);
                        warningSamplerateSnackbar.show();
                    }
                }
            }
        } else if ("err".equals(logClass) || "Emerg".equals(logClass) || "Alert".equals(logClass)
                || "Crit".equals(logClass) || "Err".equals(logClass)) {
            if (warningSamplerateSnackbar != null)
                warningSamplerateSnackbar.dismiss();
            warningSamplerateSnackbar = Snackbar.make(findViewById(R.id.myCoordinatorLayout), msg,
                    Snackbar.LENGTH_LONG);
            warningSamplerateSnackbar.show();
        }
    }

    @Override
    public void onError(SnapclientService snapclientService, String msg, Exception exception) {
        updateStartStopMenuItem();
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (resultCode == RESULT_CANCELED) {
            return;
        }
        if (requestCode == CLIENT_PROPERTIES_REQUEST) {
            Client client = null;
            try {
                client = new Client(new JSONObject(data.getStringExtra("client")));
            } catch (JSONException e) {
                e.printStackTrace();
                return;
            }
            Client clientOriginal = null;
            try {
                clientOriginal = new Client(new JSONObject(data.getStringExtra("clientOriginal")));
            } catch (JSONException e) {
                e.printStackTrace();
                return;
            }
            Log.d(TAG, "new name: " + client.getConfig().getName() + ", old name: "
                    + clientOriginal.getConfig().getName());
            if (!client.getConfig().getName().equals(clientOriginal.getConfig().getName()))
                remoteControl.setName(client, client.getConfig().getName());
            Log.d(TAG, "new stream: " + client.getConfig().getStream() + ", old stream: "
                    + clientOriginal.getConfig().getStream());
            if (!client.getConfig().getStream().equals(clientOriginal.getConfig().getStream()))
                remoteControl.setStream(client, client.getConfig().getStream());
            Log.d(TAG, "new latency: " + client.getConfig().getLatency() + ", old latency: "
                    + clientOriginal.getConfig().getLatency());
            if (client.getConfig().getLatency() != clientOriginal.getConfig().getLatency())
                remoteControl.setLatency(client, client.getConfig().getLatency());
            serverStatus.updateClient(client);
            sectionsPagerAdapter.updateServer(serverStatus);
        }
    }

    @Override
    public void onConnected(RemoteControl remoteControl) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mViewPager.setVisibility(View.VISIBLE);
            }
        });
        setActionbarSubtitle(remoteControl.getHost());
        remoteControl.getServerStatus();
        updateMenuItems(true);
    }

    @Override
    public void onConnecting(RemoteControl remoteControl) {
        setActionbarSubtitle("connecting: " + remoteControl.getHost());
    }

    @Override
    public void onDisconnected(RemoteControl remoteControl, Exception e) {
        Log.d(TAG, "onDisconnected");
        serverStatus = new ServerStatus();
        sectionsPagerAdapter.updateServer(serverStatus);
        if (e != null) {
            if (e instanceof UnknownHostException)
                setActionbarSubtitle("error: unknown host");
            else
                setActionbarSubtitle("error: " + e.getMessage());
        } else {
            setActionbarSubtitle("not connected");
        }
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mViewPager.setVisibility(View.GONE);
            }
        });
        updateMenuItems(false);
    }

    @Override
    public void onClientEvent(RemoteControl remoteControl, Client client, RemoteControl.ClientEvent event) {
        Log.d(TAG, "onClientEvent: " + event.toString());
        if (event == RemoteControl.ClientEvent.deleted)
            serverStatus.removeClient(client);
        else
            serverStatus.updateClient(client);

        sectionsPagerAdapter.updateServer(serverStatus);
    }

    @Override
    public void onServerStatus(RemoteControl remoteControl, ServerStatus serverStatus) {
        this.serverStatus = serverStatus;
        for (Stream s : serverStatus.getStreams())
            Log.d(TAG, s.toString());
        sectionsPagerAdapter.updateServer(serverStatus);
    }

    @Override
    public void onStreamUpdate(RemoteControl remoteControl, Stream stream) {
        serverStatus.updateStream(stream);
        sectionsPagerAdapter.updateServer(serverStatus);
    }

    private void setActionbarSubtitle(final String subtitle) {
        MainActivity.this.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                ActionBar actionBar = getSupportActionBar();
                if (actionBar != null)
                    actionBar.setSubtitle(subtitle);
            }
        });
    }

    private void setHost(final String host, final int streamPort, final int controlPort) {
        if (TextUtils.isEmpty(host))
            return;

        this.host = host;
        this.port = streamPort;
        this.controlPort = controlPort;
        Settings.getInstance(this).setHost(host, streamPort, controlPort);
    }

    public void updateMenuItems(final boolean connected) {
        this.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if (connected) {
                    if (miSettings != null)
                        miSettings.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
                    if (miStartStop != null)
                        miStartStop.setVisible(true);
                    //                    if (miRefresh != null)
                    //                        miRefresh.setVisible(true);
                } else {
                    if (miSettings != null)
                        miSettings.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
                    if (miStartStop != null)
                        miStartStop.setVisible(false);
                    //                    if (miRefresh != null)
                    //                        miRefresh.setVisible(false);
                }
            }
        });
    }

    @Override
    public void onResolved(NsdHelper nsdHelper, NsdServiceInfo serviceInfo) {
        Log.d(TAG, "resolved: " + serviceInfo);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            setHost(serviceInfo.getHost().getCanonicalHostName(), serviceInfo.getPort(), serviceInfo.getPort() + 1);
            startRemoteControl();
        }
        NsdHelper.getInstance(this).stopListening();
    }

    @Override
    public void onVolumeChanged(ClientItem clientItem, int percent) {
        remoteControl.setVolume(clientItem.getClient(), percent);
    }

    @Override
    public void onMute(ClientItem clientItem, boolean mute) {
        remoteControl.setMute(clientItem.getClient(), mute);
    }

    @Override
    public void onDeleteClicked(final ClientItem clientItem) {
        final Client client = clientItem.getClient();
        client.setDeleted(true);
        serverStatus.updateClient(client);
        sectionsPagerAdapter.updateServer(serverStatus);
        Snackbar mySnackbar = Snackbar.make(findViewById(R.id.myCoordinatorLayout),
                getString(R.string.client_deleted, client.getVisibleName()), Snackbar.LENGTH_SHORT);
        mySnackbar.setAction(R.string.undo_string, new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                client.setDeleted(false);
                serverStatus.updateClient(client);
                sectionsPagerAdapter.updateServer(serverStatus);
            }
        });
        mySnackbar.setCallback(new Snackbar.Callback() {
            @Override
            public void onDismissed(Snackbar snackbar, int event) {
                super.onDismissed(snackbar, event);
                if (event != DISMISS_EVENT_ACTION) {
                    remoteControl.delete(client);
                    serverStatus.removeClient(client);
                }
            }
        });
        mySnackbar.show();
    }

    @Override
    public void onPropertiesClicked(ClientItem clientItem) {
        Intent intent = new Intent(this, ClientSettingsActivity.class);
        intent.putExtra("client", clientItem.getClient().toJson().toString());
        intent.putExtra("streams", serverStatus.getJsonStreams().toString());
        intent.setFlags(0);
        startActivityForResult(intent, CLIENT_PROPERTIES_REQUEST);
    }

    @Override
    public void onStreamClicked(ClientItem clientItem, Stream stream) {
        Client client = clientItem.getClient();
        client.getConfig().setStream(stream.getId());
        remoteControl.setStream(client, client.getConfig().getStream());
        serverStatus.updateClient(client);
        sectionsPagerAdapter.updateServer(serverStatus);
    }

    /**
     * A {@link FragmentPagerAdapter} that returns a fragment corresponding to
     * one of the sections/tabs/pages.
     */
    public class SectionsPagerAdapter extends FragmentStatePagerAdapter {

        private Vector<ClientListFragment> fragments = new Vector<>();
        private int streamCount = 0;
        private boolean hideOffline = false;

        public SectionsPagerAdapter(FragmentManager fm) {
            super(fm);
        }

        public void updateServer(final ServerStatus serverStatus) {
            MainActivity.this.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Log.d(TAG, "updateServer: " + serverStatus.getStreams().size());
                    boolean changed = (serverStatus.getStreams().size() != streamCount);

                    while (serverStatus.getStreams().size() > fragments.size())
                        fragments.add(new ClientListFragment());

                    for (int i = 0; i < serverStatus.getStreams().size(); ++i) {
                        fragments.get(i).setStream(serverStatus.getStreams().get(i));
                        fragments.get(i).updateServer(serverStatus);
                    }

                    if (changed) {
                        streamCount = serverStatus.getStreams().size();
                        notifyDataSetChanged();
                        tabLayout.setTabsFromPagerAdapter(SectionsPagerAdapter.this);
                    }
                    setHideOffline(hideOffline);
                }

            });

        }

        public void setHideOffline(boolean hide) {
            this.hideOffline = hide;
            for (ClientListFragment clientListFragment : fragments)
                clientListFragment.setHideOffline(hide);
        }

        @Override
        public Fragment getItem(int position) {
            return fragments.get(position);
        }

        @Override
        public int getCount() {
            return streamCount;
        }

        @Override
        public CharSequence getPageTitle(int position) {
            return fragments.get(position).getName();
        }

    }
}