Java tutorial
/************************************************************************** * * BabyApp BabyService * * Version: 1.0.1 * * The BabyApp starts an Android background Service which measures * the current sound level as registered by the microphone of an Android Smartphone. * It will start an audio recording if the sound level exceeds a preset value. * Consequently, the app will send or upload the recorded audio files to a * predefined LAMP server running a simple PHP script. This PHP script saves * the audio file in a directory on the lamp server. * * Authors: johny.gorissen@hollandhaptics.com * r.a.van.emden@vu.nl / robinvanemden@gmail.com * * Attribution: Hans IJzerman, https://sites.google.com/site/hijzerman/ * Holland Haptics, http://myfrebble.com/ * Robin van Emden, http://www.pavlov.io/ * * License: Attribution-ShareAlike 4.0 International * http://creativecommons.org/licenses/by-sa/4.0/ * **************************************************************************/ package com.hollandhaptics.babyapp; import android.annotation.SuppressLint; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.app.TaskStackBuilder; import android.content.Context; import android.content.Intent; import android.media.MediaRecorder; import android.os.Environment; import android.os.Handler; import android.os.IBinder; import android.support.v4.app.NotificationCompat; import android.telephony.TelephonyManager; import android.util.Log; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.text.SimpleDateFormat; import java.util.Date; public class BabyService extends Service { private String TAG = "BabyService"; // A tag for log output private static final int min_free_storagespace = 10; // min % of free storage space, required for a ftp sync private static final double amplitude_threshold = 1000; // the amplitude threshold (getAmplitude returns unsigned 16-bit integer values (0-32767)) private static final int number_of_retries = 60; // the number of times to (re)try measuring an amplitude above the given threshold before stopping the recording and resetting the recorder private static final long service_runnable_interval = 1000; // The interval in ms with which the runnable is called to check the volume. private Handler mhandler; // Handlers for runnable private Runnable mrunnable; // Runnable private MediaRecorder _recorder; // MediaRecorder object private String path; // A String to store the path to the folder containing the audio files private String current_filename; // A String to store the filename of the current recording private boolean hasRecorded; // A boolean to store if the MediaRecorder has recorded something with an amplitude higher than the threshold private int attempt; // An int containing the number of attempts to try to measure an amplitude higher than the threshold private int serverResponseCode = 0; // An int to store the response code returned by the server private String upLoadServerUri; // A String to store the server's url (set as file_upload_url in strings.xml) private File[] uploadFiles; // An array to store all audio file paths when attempting to upload the files to the server private String imei; // An unique ID for each phone /** * @brief Entry point of the Service. */ @SuppressLint("HardwareIds") @Override public void onCreate() { super.onCreate(); Log.d(TAG, "BabyService Created"); TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); imei = telephonyManager.getDeviceId(); Log.d(TAG, "IMEI: " + imei); upLoadServerUri = getResources().getString(R.string.file_upload_url); mhandler = new Handler(); // Get the directory for the app's audio recordings. File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath(), "BabyApp"); if (!file.mkdirs()) { Log.e(TAG, "Directory not created"); } path = file.toString(); _recorder = new MediaRecorder(); } /** * @param intent intent * @param flags flags * @param startId startId * @return Returns START_STICKY. * @brief Start point of service */ @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d(TAG, "BabyService onStartCommand"); // Let the LED indicator flash so the user knows the service is running. FlashLED(); // Start a new recording. start_new_recording(); // Updates every x sec (set above with service_runnable_interval). mrunnable = new Runnable() { public void run() { // Check if we have permission to read/write files. if (!isExternalStorageWritable()) { Log.d(TAG, "No permission to write to external storage"); return; } // Check if there is enough storage space left. if (!IsThereStorageSpace()) { notifyUser("(Almost) out of storage space"); Log.d(TAG, "(Almost) out of storage space"); // No more storage space, so stop the MediaRecorder. _recorder.stop(); if (!hasRecorded) { // Nothing interesting has been recorded, thus delete the file. delete_file(current_filename); } _recorder.reset(); // Start uploading all audio files in the app's folder and start a new recording. uploadAudioFiles(); start_new_recording(); } else { // There's enough storage space so we can continue. // Returns the maximum absolute amplitude that was sampled since the last call to this method. int amplitude = _recorder.getMaxAmplitude(); Log.d(TAG, "Amplitude: " + amplitude); Log.d(TAG, "Attempt: " + attempt); if (amplitude > amplitude_threshold) { // The amplitude of the last measurement was higher than the threshold. hasRecorded = true; attempt = 0; // Continue recording. Log.d(TAG, "Continue recording."); } else { // The amplitude of the last measurement wasn't higher than the threshold. if (attempt >= number_of_retries) { // No more attempts left, stop the MediaRecorder. Log.d(TAG, "Stop recording."); _recorder.stop(); if (hasRecorded) { // Something with a high enough amplitude has been recorded, start uploading. uploadAudioFiles(); } else { // Nothing interesting has been recorded, thus delete the file. delete_file(current_filename); } _recorder.reset(); // Restart the MediaRecorder. start_new_recording(); } else { // There are some attempts left, update the counter and continue. attempt++; } } } mhandler.postDelayed(mrunnable, service_runnable_interval); } }; mhandler.postDelayed(mrunnable, service_runnable_interval); // We want this service to continue running until it is explicitly // stopped, so return sticky. return START_STICKY; } /** * @brief Configures the MediaRecorder for a new recording. * This method is called by start_new_recording() before starting the MediaRecorder. */ private void configure_recorder() { Log.d(TAG, "Configuring the MediaRecorder"); // Sets the audio source to be used for recording. _recorder.setAudioSource(MediaRecorder.AudioSource.MIC); // Sets the format of the output file produced during recording. _recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); // Sets the audio encoder to be used for recording. _recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); // Sets the audio sampling rate for recording. _recorder.setAudioSamplingRate(44100); @SuppressLint("SimpleDateFormat") SimpleDateFormat formatter = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss"); Date now = new Date(); current_filename = formatter.format(now); // Sets the path of the output file to be produced. _recorder.setOutputFile(path + "/" + imei + "_" + current_filename + ".3gpp"); // Prepares the recorder to begin capturing and encoding data. try { _recorder.prepare(); } catch (IOException e) { Log.e(TAG, "prepare() failed"); } } /** * @brief Configure the MediaRecorder and start a new recording. */ private void start_new_recording() { Log.d(TAG, "Starting a new recording."); configure_recorder(); hasRecorded = false; attempt = 0; _recorder.start(); } /** * @param filename The filename of the file. * @brief Deletes an audio file with the given filename. */ private void delete_file(String filename) { Log.d(TAG, "Delete file:" + filename); File file = new File(path + "/" + filename + ".3gpp"); final boolean delete = file.delete(); Log.d(TAG, "Delete success:" + delete); } /** * @param _message The message to show. * @brief Makes a notification for the user */ private void notifyUser(String _message) { NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.mipmap.ic_launcher).setContentTitle("Baby App").setContentText(_message); // Creates an explicit intent for an Activity in your app 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); mBuilder.setContentIntent(resultPendingIntent); NotificationManager mNotificationManager = (NotificationManager) getSystemService( Context.NOTIFICATION_SERVICE); // mId allows you to update the notification later on. // mId = 0 mNotificationManager.notify(0, mBuilder.build()); } /** * @brief Handles when the Service stops. */ @Override public void onDestroy() { Log.d(TAG, "BabyService Destroyed"); ClearLED(); super.onDestroy(); _recorder.reset(); _recorder.release(); } /** * @param intent An Intent. * @return A new BabyServiceBinder object. * @brief Binds the Service. */ @Override public IBinder onBind(Intent intent) { return new BabyServiceBinder(this); } /** * @brief Lets the LED indicator flash. */ private void FlashLED() { NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); Notification notif = new Notification(); notif.ledARGB = 0xFF0000ff; notif.flags = Notification.FLAG_SHOW_LIGHTS; notif.ledOnMS = 100; notif.ledOffMS = 900; nm.notify(0, notif); } /** * @brief Clears the LED indicator. */ private void ClearLED() { NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); nm.cancel(0); } /** * @brief Searches for audio files in the app's folder and starts uploading them to the server. */ public void uploadAudioFiles() { uploadFiles = new File(Environment.getExternalStorageDirectory().getAbsolutePath(), "BabyApp").listFiles(); new Thread(new Runnable() { public void run() { //uploadFile(uploadFilePath + "" + uploadFileName); for (File uploadFile : uploadFiles) { uploadFile(uploadFile); } } }).start(); } /** * @param sourceFile The file to upload. * @return int The server's response code. * @brief Uploads a file to the server. Is called by uploadAudioFiles(). */ public int uploadFile(File sourceFile) { HttpURLConnection conn; DataOutputStream dos; String lineEnd = "\r\n"; String twoHyphens = "--"; String boundary = "*****"; int bytesRead, bytesAvailable, bufferSize; byte[] buffer; int maxBufferSize = 1024 * 1024; String sourceFileUri = sourceFile.getAbsolutePath(); if (!sourceFile.isFile()) { Log.e("uploadFile", "Source File does not exist :" + sourceFileUri); return 0; } else { try { // open a URL connection to the Servlet FileInputStream fileInputStream = new FileInputStream(sourceFile); URL url = new URL(upLoadServerUri); // Open a HTTP connection to the URL conn = (HttpURLConnection) url.openConnection(); conn.setDoInput(true); // Allow Inputs conn.setDoOutput(true); // Allow Outputs conn.setUseCaches(false); // Don't use a Cached Copy conn.setRequestMethod("POST"); conn.setRequestProperty("Connection", "Keep-Alive"); conn.setRequestProperty("ENCTYPE", "multipart/form-data"); conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary); conn.setRequestProperty("fileToUpload", sourceFileUri); dos = new DataOutputStream(conn.getOutputStream()); dos.writeBytes(twoHyphens + boundary + lineEnd); dos.writeBytes("Content-Disposition: form-data; name=\"fileToUpload\";filename=\"" + sourceFileUri + "\"" + lineEnd); dos.writeBytes(lineEnd); // create a buffer of maximum size bytesAvailable = fileInputStream.available(); bufferSize = Math.min(bytesAvailable, maxBufferSize); buffer = new byte[bufferSize]; // read file and write it into form... bytesRead = fileInputStream.read(buffer, 0, bufferSize); while (bytesRead > 0) { dos.write(buffer, 0, bufferSize); bytesAvailable = fileInputStream.available(); bufferSize = Math.min(bytesAvailable, maxBufferSize); bytesRead = fileInputStream.read(buffer, 0, bufferSize); } // send multipart form data necesssary after file data... dos.writeBytes(lineEnd); dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd); // Responses from the server (code and message) serverResponseCode = conn.getResponseCode(); String serverResponseMessage = conn.getResponseMessage(); Log.i("uploadFile", "HTTP Response is : " + serverResponseMessage + ": " + serverResponseCode); if (serverResponseCode == 200) { boolean deleted = sourceFile.delete(); Log.i("Deleted succes", String.valueOf(deleted)); } //close the streams // fileInputStream.close(); dos.flush(); dos.close(); } catch (MalformedURLException ex) { ex.printStackTrace(); Log.e("Upload file to server", "error: " + ex.getMessage(), ex); } catch (Exception e) { e.printStackTrace(); Log.e("Upload file to server", "Exception : " + e.getMessage(), e); } return serverResponseCode; } // End else block } /** * @return true Enough. false Too little. * @brief Determines if there is enough storage space. */ public boolean IsThereStorageSpace() { // Is the amount of free space <= min_free_storagespace% of the total space, return false. Otherwise return true; long freespace = Environment.getExternalStorageDirectory().getFreeSpace() / 1048576; long totalspace = Environment.getExternalStorageDirectory().getTotalSpace() / 1048576; Log.d(TAG, "StorageSpace: " + String.valueOf((int) (((double) freespace / (double) totalspace) * 100)) + "% remaining."); return freespace > ((totalspace / 100) * min_free_storagespace); } /** * @return True/false. * @brief Checks if external storage is available for read and write. */ public boolean isExternalStorageWritable() { String state = Environment.getExternalStorageState(); return Environment.MEDIA_MOUNTED.equals(state); } }