com.oceansky.yellow.app.fragments.RecognitionFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.oceansky.yellow.app.fragments.RecognitionFragment.java

Source

/*
 * Copyright (C) 2014 Fastboot Mobile, LLC.
 *
 * 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 com.oceansky.yellow.app.fragments;

import android.Manifest;
import android.app.Activity;
import android.app.SearchManager;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.CardView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;

import com.oceansky.yellow.app.MainActivity;
import com.oceansky.yellow.app.R;
import com.oceansky.yellow.app.SearchActivity;
import com.oceansky.yellow.app.ui.AnimatedMicButton;
import com.oceansky.yellow.framework.EchoPrint;
import com.oceansky.yellow.providers.ProviderAggregator;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

/**
 * Fragment displaying the controls for the song fingerprinting and recognition system
 */
public class RecognitionFragment extends Fragment implements EchoPrint.PrintCallback {
    private static final String TAG = "RecognitionFragment";

    private static final int MY_PERMISSIONS_REQUEST_RECORD_AUDIO = 128;

    private static final int MSG_AUDIO_LEVEL = 1;
    private static final int MSG_RESULT = 2;
    private static final int MSG_NO_RESULT = 3;
    private static final int MSG_ERROR = 4;

    private static final int FADE_DURATION = 500;

    private EchoPrint mActivePrint;
    private EchoPrint.PrintResult mLastResult;

    private LinearLayout mButtonLayout;
    private AnimatedMicButton mRecognitionButton;
    private TextView mTvStatus;
    private TextView mTvDetails;

    private CardView mCardResult;
    private TextView mTvTitle;
    private TextView mTvArtist;
    private TextView mTvAlbum;
    private ImageView mIvArt;
    private Button mSearchButton;

    private TextView mTvOfflineError;

    private static class RecognitionHandler extends Handler {
        private WeakReference<RecognitionFragment> mParent;

        public RecognitionHandler(WeakReference<RecognitionFragment> parent) {
            mParent = parent;
        }

        @Override
        public void handleMessage(Message msg) {
            RecognitionFragment parent = mParent.get();

            if (parent != null) {
                if (msg.what == MSG_AUDIO_LEVEL) {
                    float value = (Float) msg.obj;
                    if (value >= 0.0f) {
                        parent.setVoiceLevel(value);
                    }
                } else if (msg.what == MSG_RESULT) {
                    parent.showLastResult();
                } else if (msg.what == MSG_NO_RESULT) {
                    parent.onNoResults();
                } else if (msg.what == MSG_ERROR) {
                    parent.showErrorToast();
                    parent.onNoResults();
                    parent.mHandler.removeCallbacks(parent.mStopRecognition);
                }
            }
        }
    }

    private RecognitionHandler mHandler;

    private Runnable mStopRecognition = new Runnable() {
        @Override
        public void run() {
            // As long as we haven't received either onNoMatch or onResult, the audio data
            // should be in a processing state
            mActivePrint.stopRecording();
            mRecognitionButton.setActive(false);
            onStoppedAndRecognizing();
        }
    };

    /**
     * Use this factory method to create a new instance of
     * this fragment using the provided parameters.
     *
     * @return A new instance of fragment RecognitionFragment.
     */
    public static RecognitionFragment newInstance() {
        return new RecognitionFragment();
    }

    public RecognitionFragment() {
        // Required empty public constructor
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mHandler = new RecognitionHandler(new WeakReference<>(this));
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        RelativeLayout root = (RelativeLayout) inflater.inflate(R.layout.fragment_recognition, container, false);

        // Recognition layout
        mButtonLayout = (LinearLayout) root.findViewById(R.id.llRecognitionButton);
        mRecognitionButton = (AnimatedMicButton) root.findViewById(R.id.btnStartRec);
        mTvStatus = (TextView) root.findViewById(R.id.tvStatus);
        mTvDetails = (TextView) root.findViewById(R.id.tvDetailsText);

        // Result layout
        mCardResult = (CardView) root.findViewById(R.id.cardResult);
        mTvAlbum = (TextView) root.findViewById(R.id.tvAlbumName);
        mTvArtist = (TextView) root.findViewById(R.id.tvArtistName);
        mTvTitle = (TextView) root.findViewById(R.id.tvTrackName);
        mIvArt = (ImageView) root.findViewById(R.id.ivRecognitionArt);
        mSearchButton = (Button) root.findViewById(R.id.btnSearch);

        mTvOfflineError = (TextView) root.findViewById(R.id.tvErrorMessage);
        mTvOfflineError.setText(R.string.error_recognition_unavailable_offline);

        ProviderAggregator aggregator = ProviderAggregator.getDefault();
        mTvOfflineError.setVisibility(aggregator.isOfflineMode() ? View.VISIBLE : View.GONE);

        mRecognitionButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (mActivePrint == null) {
                    // Ensure we have the permissions to record audio
                    if (ContextCompat.checkSelfPermission(getActivity(),
                            Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
                        // No explanation needed, we can request the permission.

                        ActivityCompat.requestPermissions(getActivity(),
                                new String[] { Manifest.permission.RECORD_AUDIO },
                                MY_PERMISSIONS_REQUEST_RECORD_AUDIO);
                    } else {
                        startRecording();
                    }
                } else {
                    mHandler.removeCallbacks(mStopRecognition);
                    mStopRecognition.run();
                }
            }
        });

        mSearchButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent search = new Intent(getActivity(), SearchActivity.class);
                search.setAction(Intent.ACTION_SEARCH);
                search.putExtra(SearchManager.QUERY, mLastResult.ArtistName + " " + mLastResult.TrackName);
                startActivity(search);
            }
        });

        return root;
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        MainActivity mainActivity = (MainActivity) activity;
        mainActivity.onSectionAttached(MainActivity.SECTION_RECOGNITION);
    }

    private void startRecording() {
        mActivePrint = new EchoPrint(RecognitionFragment.this);
        mActivePrint.startRecording();
        onRecognitionStartUI();

        // The buffer has a max size of 20 seconds, so we force stop at around 19 seconds
        mHandler.postDelayed(mStopRecognition, 19000);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[],
            @NonNull int[] grantResults) {
        switch (requestCode) {
        case MY_PERMISSIONS_REQUEST_RECORD_AUDIO: {
            // If request is cancelled, the result arrays are empty.
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                startRecording();
            }
            break;
        }
        }
    }

    @Override
    public void onResult(EchoPrint.PrintResult result) {
        mLastResult = result;
        mHandler.sendEmptyMessage(MSG_RESULT);
        mHandler.removeCallbacks(mStopRecognition);
    }

    @Override
    public void onNoMatch() {
        mHandler.sendEmptyMessage(MSG_NO_RESULT);
    }

    @Override
    public void onAudioLevel(final float level) {
        mHandler.obtainMessage(MSG_AUDIO_LEVEL, level).sendToTarget();
    }

    @Override
    public void onError() {
        mHandler.obtainMessage(MSG_ERROR).sendToTarget();
    }

    private void onStoppedAndRecognizing() {
        onRecognitionStopUI();
    }

    private void loadAlbumArt(final String urlString, final ImageView iv) {
        new Thread() {
            public void run() {
                URL url;
                try {
                    url = new URL(urlString);
                } catch (MalformedURLException e) {
                    // Too bad
                    mHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            mIvArt.setImageResource(R.drawable.album_placeholder);
                        }
                    });
                    return;
                }

                Log.d(TAG, "Loading album art: " + urlString);

                try {
                    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                    InputStream is = conn.getInputStream();
                    byte[] buffer = new byte[8192];
                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    int read;
                    while ((read = is.read(buffer)) > 0) {
                        baos.write(buffer, 0, read);
                    }

                    final Bitmap bmp = BitmapFactory.decodeByteArray(baos.toByteArray(), 0, baos.size());
                    if (bmp != null) {
                        mHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                iv.setImageBitmap(bmp);
                                iv.setVisibility(View.VISIBLE);
                            }
                        });
                    } else {
                        Log.e(TAG, "Null bitmap from image");
                    }
                } catch (IOException e) {
                    Log.e(TAG, "Error downloading album art", e);
                    mHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            iv.setImageResource(R.drawable.album_placeholder);
                            iv.setVisibility(View.VISIBLE);
                        }
                    });
                }
            }
        }.start();
    }

    public void setVoiceLevel(float level) {
        mRecognitionButton.setLevel(level);
    }

    public void onRecognitionStartUI() {
        mRecognitionButton.setActive(true);
        if (mTvDetails.getAlpha() > 0) {
            // Hide the details text
            mTvDetails.animate().alpha(0).translationY(mTvDetails.getMeasuredHeight()).setDuration(FADE_DURATION)
                    .setInterpolator(new AccelerateDecelerateInterpolator()).start();
        }
        mTvStatus.setText(R.string.recognition_status_listening);
        hideResultCard();
    }

    public void onRecognitionStopUI() {
        // Disable the button and prevent clicking it while we're processing
        mRecognitionButton.setActive(false);
        mRecognitionButton.setEnabled(false);

        mTvStatus.setText(R.string.recognition_status_recognizing);
    }

    public void onNoResults() {
        mActivePrint = null;
        showNoResult();
    }

    public void showLastResult() {
        mActivePrint = null;

        showResultCard();
        mTvAlbum.setText(mLastResult.AlbumName);
        mTvTitle.setText(mLastResult.TrackName);
        mTvArtist.setText(mLastResult.ArtistName);

        mRecognitionButton.setActive(false);
        mRecognitionButton.setEnabled(true);
        mTvStatus.setText(R.string.recognition_status_idle);

        // Load the album art in a thread
        loadAlbumArt(mLastResult.AlbumImageUrl, mIvArt);
    }

    public void showResultCard() {
        mCardResult.animate().alpha(1).translationY(-mCardResult.getMeasuredHeight()).setDuration(FADE_DURATION)
                .setInterpolator(new AccelerateDecelerateInterpolator()).start();
        mButtonLayout.animate().translationY(-mCardResult.getMeasuredHeight()).setDuration(FADE_DURATION)
                .setInterpolator(new AccelerateDecelerateInterpolator()).start();
    }

    public void hideResultCard() {
        mCardResult.animate().alpha(0).translationY(mCardResult.getMeasuredHeight()).setDuration(FADE_DURATION)
                .setInterpolator(new AccelerateDecelerateInterpolator()).start();
        mButtonLayout.animate().translationY(0).setDuration(FADE_DURATION)
                .setInterpolator(new AccelerateDecelerateInterpolator()).start();
    }

    public void showNoResult() {
        mRecognitionButton.setActive(false);
        mRecognitionButton.setEnabled(true);
        mTvStatus.setText(R.string.recognition_status_no_result);
    }

    public void showErrorToast() {
        Toast.makeText(getActivity(), R.string.toast_recognition_error, Toast.LENGTH_SHORT).show();
    }
}