From 374e1a70d2e11a21c032100359bed348aa726e7c Mon Sep 17 00:00:00 2001 From: iClaude Date: Sun, 7 May 2017 16:59:19 +0200 Subject: [PATCH 01/96] initial setup --- .gitignore | 45 +++++++-- .idea/.name | 1 - .idea/compiler.xml | 23 ----- .idea/copyright/profiles_settings.xml | 3 - .idea/encodings.xml | 5 - .idea/gradle.xml | 19 ---- .idea/inspectionProfiles/Project_Default.xml | 11 --- .../inspectionProfiles/profiles_settings.xml | 7 -- .idea/misc.xml | 9 -- .idea/modules.xml | 9 -- .idea/modules/SoundRecorder.iml | 19 ---- .idea/modules/app/app.iml | 99 ------------------- .idea/scopes/scope_settings.xml | 5 - .idea/vcs.xml | 9 -- app/build.gradle | 2 +- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 +- 17 files changed, 43 insertions(+), 229 deletions(-) delete mode 100644 .idea/.name delete mode 100644 .idea/compiler.xml delete mode 100644 .idea/copyright/profiles_settings.xml delete mode 100644 .idea/encodings.xml delete mode 100644 .idea/gradle.xml delete mode 100644 .idea/inspectionProfiles/Project_Default.xml delete mode 100644 .idea/inspectionProfiles/profiles_settings.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/modules/SoundRecorder.iml delete mode 100644 .idea/modules/app/app.iml delete mode 100644 .idea/scopes/scope_settings.xml delete mode 100644 .idea/vcs.xml diff --git a/.gitignore b/.gitignore index afbdab33..a63d7458 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,39 @@ -.gradle -/local.properties -/.idea/workspace.xml -/.idea/libraries -.DS_Store -/build +# Built application files +*.apk +*.ap_ + +# Files for the Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties +keystore.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +/.idea/ +.idea/workspace.xml + +# Android Studio +*.iml \ No newline at end of file diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index 79cabe20..00000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -Sound Recorder \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index 217af471..00000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml deleted file mode 100644 index e7bedf33..00000000 --- a/.idea/copyright/profiles_settings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index e206d70d..00000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/.idea/gradle.xml b/.idea/gradle.xml deleted file mode 100644 index 2a7f5bfc..00000000 --- a/.idea/gradle.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 2071d1a6..00000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 3b312839..00000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 7d21c22c..00000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index ab86d572..00000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/SoundRecorder.iml b/.idea/modules/SoundRecorder.iml deleted file mode 100644 index e8867bf3..00000000 --- a/.idea/modules/SoundRecorder.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/app/app.iml b/.idea/modules/app/app.iml deleted file mode 100644 index af2f2f29..00000000 --- a/.idea/modules/app/app.iml +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/scopes/scope_settings.xml b/.idea/scopes/scope_settings.xml deleted file mode 100644 index 922003b8..00000000 --- a/.idea/scopes/scope_settings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index c63efbb2..00000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/app/build.gradle b/app/build.gradle index 375ed4d3..15691947 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'com.android.application' android { compileSdkVersion 21 - buildToolsVersion "21.1.2" + buildToolsVersion '25.0.0' defaultConfig { applicationId "com.danielkim.soundrecorder" diff --git a/build.gradle b/build.gradle index 6356aabd..072e693a 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:1.0.0' + classpath 'com.android.tools.build:gradle:2.3.1' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0c71e760..a7949eed 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Apr 10 15:27:10 PDT 2013 +#Fri May 05 19:51:11 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-all.zip From 5e8de3526c81ad8dcfd69bde2bbec5d20ead526d Mon Sep 17 00:00:00 2001 From: iClaude Date: Mon, 8 May 2017 14:28:33 +0200 Subject: [PATCH 02/96] build.gradle file (app) updated with new versions (libraries, version code and name, target sdk, compile sdk, etc.) --- app/build.gradle | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 15691947..983ec736 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,15 +1,15 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 21 - buildToolsVersion '25.0.0' + compileSdkVersion 25 + buildToolsVersion '25.0.2' defaultConfig { applicationId "com.danielkim.soundrecorder" minSdkVersion 16 - targetSdkVersion 21 - versionCode 8 - versionName "1.2.5" + targetSdkVersion 25 + versionCode 9 + versionName "1.3" } buildTypes { release { @@ -22,10 +22,14 @@ android { } } +ext { + libraryVersion = '25.1.1' +} + dependencies { - compile 'com.android.support:appcompat-v7:21.0.2' - compile 'com.android.support:cardview-v7:21.0.+' - compile 'com.android.support:recyclerview-v7:21.0.+' + compile "com.android.support:appcompat-v7:$libraryVersion" + compile "com.android.support:cardview-v7:$libraryVersion" + compile "com.android.support:recyclerview-v7:$libraryVersion" compile 'com.melnykov:floatingactionbutton:1.1.0' compile 'com.jpardogo.materialtabstrip:library:1.0.6' } From 77f3b4cdb921bc471e5f45a1669841787bf37019 Mon Sep 17 00:00:00 2001 From: iClaude Date: Mon, 8 May 2017 14:32:20 +0200 Subject: [PATCH 03/96] build.gradle file , etc.)(app) updated with increased installation timeout --- app/build.gradle | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/build.gradle b/app/build.gradle index 983ec736..48b0a829 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -20,6 +20,11 @@ android { lintOptions{ disable 'MissingTranslation' } + + // Timeout for installing the app on the device + adbOptions { + timeOutInMs = 30 * 1000 + } } ext { From cd9786b2c8db23455f514cb905b54fa1c6e0c549 Mon Sep 17 00:00:00 2001 From: iClaude Date: Mon, 8 May 2017 15:26:55 +0200 Subject: [PATCH 04/96] database schema updated in contract class RecordingsContract --- .../soundrecorder/RecordingService.java | 1 + .../adapters/FileViewerAdapter.java | 7 +- .../{ => database}/DBHelper.java | 83 ++++++++++--------- .../database/RecordingsContract.java | 32 +++++++ 4 files changed, 78 insertions(+), 45 deletions(-) rename app/src/main/java/com/danielkim/soundrecorder/{ => database}/DBHelper.java (97%) create mode 100644 app/src/main/java/com/danielkim/soundrecorder/database/RecordingsContract.java diff --git a/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java b/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java index 339e5f61..23c3bb32 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java +++ b/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java @@ -14,6 +14,7 @@ import android.widget.Toast; import com.danielkim.soundrecorder.activities.MainActivity; +import com.danielkim.soundrecorder.database.DBHelper; import java.io.File; import java.io.IOException; diff --git a/app/src/main/java/com/danielkim/soundrecorder/adapters/FileViewerAdapter.java b/app/src/main/java/com/danielkim/soundrecorder/adapters/FileViewerAdapter.java index 1d1418cf..0aa26272 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/adapters/FileViewerAdapter.java +++ b/app/src/main/java/com/danielkim/soundrecorder/adapters/FileViewerAdapter.java @@ -10,6 +10,7 @@ import android.support.v4.app.FragmentTransaction; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; +import android.text.format.DateUtils; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -17,18 +18,16 @@ import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; -import android.text.format.DateUtils; -import com.danielkim.soundrecorder.DBHelper; import com.danielkim.soundrecorder.R; import com.danielkim.soundrecorder.RecordingItem; +import com.danielkim.soundrecorder.database.DBHelper; import com.danielkim.soundrecorder.fragments.PlaybackFragment; import com.danielkim.soundrecorder.listeners.OnDatabaseChangedListener; import java.io.File; -import java.util.Locale; -import java.util.concurrent.TimeUnit; import java.util.ArrayList; +import java.util.concurrent.TimeUnit; /** * Created by Daniel on 12/29/2014. diff --git a/app/src/main/java/com/danielkim/soundrecorder/DBHelper.java b/app/src/main/java/com/danielkim/soundrecorder/database/DBHelper.java similarity index 97% rename from app/src/main/java/com/danielkim/soundrecorder/DBHelper.java rename to app/src/main/java/com/danielkim/soundrecorder/database/DBHelper.java index 6f0bb7a3..5814d7ca 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/DBHelper.java +++ b/app/src/main/java/com/danielkim/soundrecorder/database/DBHelper.java @@ -1,4 +1,4 @@ -package com.danielkim.soundrecorder; +package com.danielkim.soundrecorder.database; import android.content.ContentValues; import android.content.Context; @@ -7,6 +7,7 @@ import android.database.sqlite.SQLiteOpenHelper; import android.provider.BaseColumns; +import com.danielkim.soundrecorder.RecordingItem; import com.danielkim.soundrecorder.listeners.OnDatabaseChangedListener; import java.util.Comparator; @@ -16,11 +17,9 @@ */ public class DBHelper extends SQLiteOpenHelper { private Context mContext; - - private static final String LOG_TAG = "DBHelper"; - private static OnDatabaseChangedListener mOnDatabaseChangedListener; + private static final String LOG_TAG = "DBHelper"; public static final String DATABASE_NAME = "saved_recordings.db"; private static final int DATABASE_VERSION = 1; @@ -46,6 +45,12 @@ public static abstract class DBHelperItem implements BaseColumns { @SuppressWarnings("unused") private static final String SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS " + DBHelperItem.TABLE_NAME; + + public DBHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + mContext = context; + } + @Override public void onCreate(SQLiteDatabase db) { db.execSQL(SQL_CREATE_ENTRIES); @@ -56,15 +61,34 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } - public DBHelper(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); - mContext = context; - } public static void setOnDatabaseChangedListener(OnDatabaseChangedListener listener) { mOnDatabaseChangedListener = listener; } + public long addRecording(String recordingName, String filePath, long length) { + + SQLiteDatabase db = getWritableDatabase(); + ContentValues cv = new ContentValues(); + cv.put(DBHelperItem.COLUMN_NAME_RECORDING_NAME, recordingName); + cv.put(DBHelperItem.COLUMN_NAME_RECORDING_FILE_PATH, filePath); + cv.put(DBHelperItem.COLUMN_NAME_RECORDING_LENGTH, length); + cv.put(DBHelperItem.COLUMN_NAME_TIME_ADDED, System.currentTimeMillis()); + long rowId = db.insert(DBHelperItem.TABLE_NAME, null, cv); + + if (mOnDatabaseChangedListener != null) { + mOnDatabaseChangedListener.onNewDatabaseEntryAdded(); + } + + return rowId; + } + + public void removeItemWithId(int id) { + SQLiteDatabase db = getWritableDatabase(); + String[] whereArgs = {String.valueOf(id)}; + db.delete(DBHelperItem.TABLE_NAME, "_ID=?", whereArgs); + } + public RecordingItem getItemAt(int position) { SQLiteDatabase db = getReadableDatabase(); String[] projection = { @@ -88,10 +112,17 @@ public RecordingItem getItemAt(int position) { return null; } - public void removeItemWithId(int id) { + public void renameItem(RecordingItem item, String recordingName, String filePath) { SQLiteDatabase db = getWritableDatabase(); - String[] whereArgs = { String.valueOf(id) }; - db.delete(DBHelperItem.TABLE_NAME, "_ID=?", whereArgs); + ContentValues cv = new ContentValues(); + cv.put(DBHelperItem.COLUMN_NAME_RECORDING_NAME, recordingName); + cv.put(DBHelperItem.COLUMN_NAME_RECORDING_FILE_PATH, filePath); + db.update(DBHelperItem.TABLE_NAME, cv, + DBHelperItem._ID + "=" + item.getId(), null); + + if (mOnDatabaseChangedListener != null) { + mOnDatabaseChangedListener.onDatabaseEntryRenamed(); + } } public int getCount() { @@ -115,36 +146,6 @@ public int compare(RecordingItem item1, RecordingItem item2) { } } - public long addRecording(String recordingName, String filePath, long length) { - - SQLiteDatabase db = getWritableDatabase(); - ContentValues cv = new ContentValues(); - cv.put(DBHelperItem.COLUMN_NAME_RECORDING_NAME, recordingName); - cv.put(DBHelperItem.COLUMN_NAME_RECORDING_FILE_PATH, filePath); - cv.put(DBHelperItem.COLUMN_NAME_RECORDING_LENGTH, length); - cv.put(DBHelperItem.COLUMN_NAME_TIME_ADDED, System.currentTimeMillis()); - long rowId = db.insert(DBHelperItem.TABLE_NAME, null, cv); - - if (mOnDatabaseChangedListener != null) { - mOnDatabaseChangedListener.onNewDatabaseEntryAdded(); - } - - return rowId; - } - - public void renameItem(RecordingItem item, String recordingName, String filePath) { - SQLiteDatabase db = getWritableDatabase(); - ContentValues cv = new ContentValues(); - cv.put(DBHelperItem.COLUMN_NAME_RECORDING_NAME, recordingName); - cv.put(DBHelperItem.COLUMN_NAME_RECORDING_FILE_PATH, filePath); - db.update(DBHelperItem.TABLE_NAME, cv, - DBHelperItem._ID + "=" + item.getId(), null); - - if (mOnDatabaseChangedListener != null) { - mOnDatabaseChangedListener.onDatabaseEntryRenamed(); - } - } - public long restoreRecording(RecordingItem item) { SQLiteDatabase db = getWritableDatabase(); ContentValues cv = new ContentValues(); diff --git a/app/src/main/java/com/danielkim/soundrecorder/database/RecordingsContract.java b/app/src/main/java/com/danielkim/soundrecorder/database/RecordingsContract.java new file mode 100644 index 00000000..ecaba332 --- /dev/null +++ b/app/src/main/java/com/danielkim/soundrecorder/database/RecordingsContract.java @@ -0,0 +1,32 @@ +package com.danielkim.soundrecorder.database; + +import android.provider.BaseColumns; + +/** + * Created by iClaude on 08/05/2017. + */ + +public class RecordingsContract { + + // Table "saved_recordings". + public static class TableSavedRecording implements BaseColumns { + public static final String TABLE_NAME = "saved_recordings"; + + public static final String COLUMN_NAME_RECORDING_NAME = "recording_name"; + public static final String COLUMN_NAME_RECORDING_FILE_PATH = "file_path"; + public static final String COLUMN_NAME_RECORDING_LENGTH = "length"; + public static final String COLUMN_NAME_TIME_ADDED = "time_added"; + } + + // Table "scheduled_recordings". + public static class TableScheduledRecording implements BaseColumns { + public static final String TABLE_NAME = "scheduled_recordings"; + + public static final String COLUMN_NAME_START = "start"; // start of the recording in ms from epoch + public static final String COLUMN_NAME_LENGTH = "length"; // length of the recording in ms + } + + + private RecordingsContract() { + } +} From b2460f23911a4ff901563dbdd976a744d53a3bd8 Mon Sep 17 00:00:00 2001 From: iClaude Date: Mon, 8 May 2017 15:49:29 +0200 Subject: [PATCH 05/96] database - SQL strings put in a separate file and DBHelper class updated --- .../soundrecorder/database/DBHelper.java | 86 +++++++------------ .../soundrecorder/database/SQLStrings.java | 36 ++++++++ 2 files changed, 69 insertions(+), 53 deletions(-) create mode 100644 app/src/main/java/com/danielkim/soundrecorder/database/SQLStrings.java diff --git a/app/src/main/java/com/danielkim/soundrecorder/database/DBHelper.java b/app/src/main/java/com/danielkim/soundrecorder/database/DBHelper.java index 5814d7ca..70aba972 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/database/DBHelper.java +++ b/app/src/main/java/com/danielkim/soundrecorder/database/DBHelper.java @@ -5,13 +5,15 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; -import android.provider.BaseColumns; import com.danielkim.soundrecorder.RecordingItem; import com.danielkim.soundrecorder.listeners.OnDatabaseChangedListener; import java.util.Comparator; +import static com.danielkim.soundrecorder.database.RecordingsContract.TableSavedRecording; +import static com.danielkim.soundrecorder.database.SQLStrings.CREATE_TABLE_SAVED_RECORDINGS; + /** * Created by Daniel on 12/29/2014. */ @@ -23,28 +25,6 @@ public class DBHelper extends SQLiteOpenHelper { public static final String DATABASE_NAME = "saved_recordings.db"; private static final int DATABASE_VERSION = 1; - public static abstract class DBHelperItem implements BaseColumns { - public static final String TABLE_NAME = "saved_recordings"; - - public static final String COLUMN_NAME_RECORDING_NAME = "recording_name"; - public static final String COLUMN_NAME_RECORDING_FILE_PATH = "file_path"; - public static final String COLUMN_NAME_RECORDING_LENGTH = "length"; - public static final String COLUMN_NAME_TIME_ADDED = "time_added"; - } - - private static final String TEXT_TYPE = " TEXT"; - private static final String COMMA_SEP = ","; - private static final String SQL_CREATE_ENTRIES = - "CREATE TABLE " + DBHelperItem.TABLE_NAME + " (" + - DBHelperItem._ID + " INTEGER PRIMARY KEY" + COMMA_SEP + - DBHelperItem.COLUMN_NAME_RECORDING_NAME + TEXT_TYPE + COMMA_SEP + - DBHelperItem.COLUMN_NAME_RECORDING_FILE_PATH + TEXT_TYPE + COMMA_SEP + - DBHelperItem.COLUMN_NAME_RECORDING_LENGTH + " INTEGER " + COMMA_SEP + - DBHelperItem.COLUMN_NAME_TIME_ADDED + " INTEGER " + ")"; - - @SuppressWarnings("unused") - private static final String SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS " + DBHelperItem.TABLE_NAME; - public DBHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); @@ -53,7 +33,7 @@ public DBHelper(Context context) { @Override public void onCreate(SQLiteDatabase db) { - db.execSQL(SQL_CREATE_ENTRIES); + db.execSQL(CREATE_TABLE_SAVED_RECORDINGS); } @Override @@ -70,11 +50,11 @@ public long addRecording(String recordingName, String filePath, long length) { SQLiteDatabase db = getWritableDatabase(); ContentValues cv = new ContentValues(); - cv.put(DBHelperItem.COLUMN_NAME_RECORDING_NAME, recordingName); - cv.put(DBHelperItem.COLUMN_NAME_RECORDING_FILE_PATH, filePath); - cv.put(DBHelperItem.COLUMN_NAME_RECORDING_LENGTH, length); - cv.put(DBHelperItem.COLUMN_NAME_TIME_ADDED, System.currentTimeMillis()); - long rowId = db.insert(DBHelperItem.TABLE_NAME, null, cv); + cv.put(TableSavedRecording.COLUMN_NAME_RECORDING_NAME, recordingName); + cv.put(TableSavedRecording.COLUMN_NAME_RECORDING_FILE_PATH, filePath); + cv.put(TableSavedRecording.COLUMN_NAME_RECORDING_LENGTH, length); + cv.put(TableSavedRecording.COLUMN_NAME_TIME_ADDED, System.currentTimeMillis()); + long rowId = db.insert(TableSavedRecording.TABLE_NAME, null, cv); if (mOnDatabaseChangedListener != null) { mOnDatabaseChangedListener.onNewDatabaseEntryAdded(); @@ -86,26 +66,26 @@ public long addRecording(String recordingName, String filePath, long length) { public void removeItemWithId(int id) { SQLiteDatabase db = getWritableDatabase(); String[] whereArgs = {String.valueOf(id)}; - db.delete(DBHelperItem.TABLE_NAME, "_ID=?", whereArgs); + db.delete(TableSavedRecording.TABLE_NAME, "_ID=?", whereArgs); } public RecordingItem getItemAt(int position) { SQLiteDatabase db = getReadableDatabase(); String[] projection = { - DBHelperItem._ID, - DBHelperItem.COLUMN_NAME_RECORDING_NAME, - DBHelperItem.COLUMN_NAME_RECORDING_FILE_PATH, - DBHelperItem.COLUMN_NAME_RECORDING_LENGTH, - DBHelperItem.COLUMN_NAME_TIME_ADDED + TableSavedRecording._ID, + TableSavedRecording.COLUMN_NAME_RECORDING_NAME, + TableSavedRecording.COLUMN_NAME_RECORDING_FILE_PATH, + TableSavedRecording.COLUMN_NAME_RECORDING_LENGTH, + TableSavedRecording.COLUMN_NAME_TIME_ADDED }; - Cursor c = db.query(DBHelperItem.TABLE_NAME, projection, null, null, null, null, null); + Cursor c = db.query(TableSavedRecording.TABLE_NAME, projection, null, null, null, null, null); if (c.moveToPosition(position)) { RecordingItem item = new RecordingItem(); - item.setId(c.getInt(c.getColumnIndex(DBHelperItem._ID))); - item.setName(c.getString(c.getColumnIndex(DBHelperItem.COLUMN_NAME_RECORDING_NAME))); - item.setFilePath(c.getString(c.getColumnIndex(DBHelperItem.COLUMN_NAME_RECORDING_FILE_PATH))); - item.setLength(c.getInt(c.getColumnIndex(DBHelperItem.COLUMN_NAME_RECORDING_LENGTH))); - item.setTime(c.getLong(c.getColumnIndex(DBHelperItem.COLUMN_NAME_TIME_ADDED))); + item.setId(c.getInt(c.getColumnIndex(TableSavedRecording._ID))); + item.setName(c.getString(c.getColumnIndex(TableSavedRecording.COLUMN_NAME_RECORDING_NAME))); + item.setFilePath(c.getString(c.getColumnIndex(TableSavedRecording.COLUMN_NAME_RECORDING_FILE_PATH))); + item.setLength(c.getInt(c.getColumnIndex(TableSavedRecording.COLUMN_NAME_RECORDING_LENGTH))); + item.setTime(c.getLong(c.getColumnIndex(TableSavedRecording.COLUMN_NAME_TIME_ADDED))); c.close(); return item; } @@ -115,10 +95,10 @@ public RecordingItem getItemAt(int position) { public void renameItem(RecordingItem item, String recordingName, String filePath) { SQLiteDatabase db = getWritableDatabase(); ContentValues cv = new ContentValues(); - cv.put(DBHelperItem.COLUMN_NAME_RECORDING_NAME, recordingName); - cv.put(DBHelperItem.COLUMN_NAME_RECORDING_FILE_PATH, filePath); - db.update(DBHelperItem.TABLE_NAME, cv, - DBHelperItem._ID + "=" + item.getId(), null); + cv.put(TableSavedRecording.COLUMN_NAME_RECORDING_NAME, recordingName); + cv.put(TableSavedRecording.COLUMN_NAME_RECORDING_FILE_PATH, filePath); + db.update(TableSavedRecording.TABLE_NAME, cv, + TableSavedRecording._ID + "=" + item.getId(), null); if (mOnDatabaseChangedListener != null) { mOnDatabaseChangedListener.onDatabaseEntryRenamed(); @@ -127,8 +107,8 @@ public void renameItem(RecordingItem item, String recordingName, String filePath public int getCount() { SQLiteDatabase db = getReadableDatabase(); - String[] projection = { DBHelperItem._ID }; - Cursor c = db.query(DBHelperItem.TABLE_NAME, projection, null, null, null, null, null); + String[] projection = {TableSavedRecording._ID}; + Cursor c = db.query(TableSavedRecording.TABLE_NAME, projection, null, null, null, null, null); int count = c.getCount(); c.close(); return count; @@ -149,12 +129,12 @@ public int compare(RecordingItem item1, RecordingItem item2) { public long restoreRecording(RecordingItem item) { SQLiteDatabase db = getWritableDatabase(); ContentValues cv = new ContentValues(); - cv.put(DBHelperItem.COLUMN_NAME_RECORDING_NAME, item.getName()); - cv.put(DBHelperItem.COLUMN_NAME_RECORDING_FILE_PATH, item.getFilePath()); - cv.put(DBHelperItem.COLUMN_NAME_RECORDING_LENGTH, item.getLength()); - cv.put(DBHelperItem.COLUMN_NAME_TIME_ADDED, item.getTime()); - cv.put(DBHelperItem._ID, item.getId()); - long rowId = db.insert(DBHelperItem.TABLE_NAME, null, cv); + cv.put(TableSavedRecording.COLUMN_NAME_RECORDING_NAME, item.getName()); + cv.put(TableSavedRecording.COLUMN_NAME_RECORDING_FILE_PATH, item.getFilePath()); + cv.put(TableSavedRecording.COLUMN_NAME_RECORDING_LENGTH, item.getLength()); + cv.put(TableSavedRecording.COLUMN_NAME_TIME_ADDED, item.getTime()); + cv.put(TableSavedRecording._ID, item.getId()); + long rowId = db.insert(TableSavedRecording.TABLE_NAME, null, cv); if (mOnDatabaseChangedListener != null) { //mOnDatabaseChangedListener.onNewDatabaseEntryAdded(); } diff --git a/app/src/main/java/com/danielkim/soundrecorder/database/SQLStrings.java b/app/src/main/java/com/danielkim/soundrecorder/database/SQLStrings.java new file mode 100644 index 00000000..5a84fa62 --- /dev/null +++ b/app/src/main/java/com/danielkim/soundrecorder/database/SQLStrings.java @@ -0,0 +1,36 @@ +package com.danielkim.soundrecorder.database; + +/** + * Created by agost on 08/05/2017. + */ + +public interface SQLStrings { + // Utility. + String TEXT_TYPE = " TEXT"; + String INTEGER_TYPE = " INTEGER"; + String COMMA_SEP = ","; + + // Table "saved_recordings": create and delete. + String CREATE_TABLE_SAVED_RECORDINGS = + "CREATE TABLE " + RecordingsContract.TableSavedRecording.TABLE_NAME + " (" + + RecordingsContract.TableSavedRecording._ID + " INTEGER PRIMARY KEY" + COMMA_SEP + + RecordingsContract.TableSavedRecording.COLUMN_NAME_RECORDING_NAME + TEXT_TYPE + COMMA_SEP + + RecordingsContract.TableSavedRecording.COLUMN_NAME_RECORDING_FILE_PATH + TEXT_TYPE + COMMA_SEP + + RecordingsContract.TableSavedRecording.COLUMN_NAME_RECORDING_LENGTH + INTEGER_TYPE + COMMA_SEP + + RecordingsContract.TableSavedRecording.COLUMN_NAME_TIME_ADDED + INTEGER_TYPE + ")"; + + @SuppressWarnings("unused") + String DELETE_TABLE_SAVED_RECORDINGS = "DROP TABLE IF EXISTS " + RecordingsContract.TableSavedRecording.TABLE_NAME; + + // Table "scheduled_recordings": create and delete. + String CREATE_TABLE_SCHEDULED_RECORDINGS = + "CREATE TABLE " + RecordingsContract.TableScheduledRecording.TABLE_NAME + " (" + + RecordingsContract.TableScheduledRecording._ID + " INTEGER PRIMARY KEY" + COMMA_SEP + + RecordingsContract.TableScheduledRecording.COLUMN_NAME_START + INTEGER_TYPE + COMMA_SEP + + RecordingsContract.TableScheduledRecording.COLUMN_NAME_LENGTH + INTEGER_TYPE + ")"; + + @SuppressWarnings("unused") + String DELETE_TABLE_SCHEDULED_RECORDINGS = "DROP TABLE IF EXISTS " + RecordingsContract.TableScheduledRecording.TABLE_NAME; + + +} From 79dc7839cffba4b95db236399237744b8a8c90a1 Mon Sep 17 00:00:00 2001 From: iClaude Date: Mon, 8 May 2017 16:03:39 +0200 Subject: [PATCH 06/96] database - DBHelper class - method names changed --- .../com/danielkim/soundrecorder/RecordingService.java | 2 +- .../soundrecorder/adapters/FileViewerAdapter.java | 8 ++++---- .../com/danielkim/soundrecorder/database/DBHelper.java | 9 ++++----- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java b/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java index 23c3bb32..7309de06 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java +++ b/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java @@ -108,7 +108,7 @@ public void setFileNameAndPath(){ count++; mFileName = getString(R.string.default_file_name) - + " #" + (mDatabase.getCount() + count) + ".mp4"; + + " #" + (mDatabase.getSavedRecordingsCount() + count) + ".mp4"; mFilePath = Environment.getExternalStorageDirectory().getAbsolutePath(); mFilePath += "/SoundRecorder/" + mFileName; diff --git a/app/src/main/java/com/danielkim/soundrecorder/adapters/FileViewerAdapter.java b/app/src/main/java/com/danielkim/soundrecorder/adapters/FileViewerAdapter.java index 0aa26272..2f42df00 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/adapters/FileViewerAdapter.java +++ b/app/src/main/java/com/danielkim/soundrecorder/adapters/FileViewerAdapter.java @@ -162,11 +162,11 @@ public RecordingsViewHolder(View v) { @Override public int getItemCount() { - return mDatabase.getCount(); + return mDatabase.getSavedRecordingsCount(); } public RecordingItem getItem(int position) { - return mDatabase.getItemAt(position); + return mDatabase.getRecording(position); } @Override @@ -198,7 +198,7 @@ public void remove(int position) { Toast.LENGTH_SHORT ).show(); - mDatabase.removeItemWithId(getItem(position).getId()); + mDatabase.removeRecording(getItem(position).getId()); notifyItemRemoved(position); } @@ -224,7 +224,7 @@ public void rename(int position, String name) { //file name is unique, rename file File oldFilePath = new File(getItem(position).getFilePath()); oldFilePath.renameTo(f); - mDatabase.renameItem(getItem(position), name, mFilePath); + mDatabase.updateRecording(getItem(position), name, mFilePath); notifyItemChanged(position); } } diff --git a/app/src/main/java/com/danielkim/soundrecorder/database/DBHelper.java b/app/src/main/java/com/danielkim/soundrecorder/database/DBHelper.java index 70aba972..13c20221 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/database/DBHelper.java +++ b/app/src/main/java/com/danielkim/soundrecorder/database/DBHelper.java @@ -41,7 +41,6 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } - public static void setOnDatabaseChangedListener(OnDatabaseChangedListener listener) { mOnDatabaseChangedListener = listener; } @@ -63,13 +62,13 @@ public long addRecording(String recordingName, String filePath, long length) { return rowId; } - public void removeItemWithId(int id) { + public void removeRecording(int id) { SQLiteDatabase db = getWritableDatabase(); String[] whereArgs = {String.valueOf(id)}; db.delete(TableSavedRecording.TABLE_NAME, "_ID=?", whereArgs); } - public RecordingItem getItemAt(int position) { + public RecordingItem getRecording(int position) { SQLiteDatabase db = getReadableDatabase(); String[] projection = { TableSavedRecording._ID, @@ -92,7 +91,7 @@ public RecordingItem getItemAt(int position) { return null; } - public void renameItem(RecordingItem item, String recordingName, String filePath) { + public void updateRecording(RecordingItem item, String recordingName, String filePath) { SQLiteDatabase db = getWritableDatabase(); ContentValues cv = new ContentValues(); cv.put(TableSavedRecording.COLUMN_NAME_RECORDING_NAME, recordingName); @@ -105,7 +104,7 @@ public void renameItem(RecordingItem item, String recordingName, String filePath } } - public int getCount() { + public int getSavedRecordingsCount() { SQLiteDatabase db = getReadableDatabase(); String[] projection = {TableSavedRecording._ID}; Cursor c = db.query(TableSavedRecording.TABLE_NAME, projection, null, null, null, null, null); From 8fc88ee7a4cd5960060bffaf8832a090152ed984 Mon Sep 17 00:00:00 2001 From: iClaude Date: Mon, 8 May 2017 20:53:50 +0200 Subject: [PATCH 07/96] NDC (non definitive commit) - working on permissions for Marshmallow+ --- .../fragments/RecordFragment.java | 43 ++++++++++++++++++- app/src/main/res/values-v21/styles.xml | 3 +- others/notes | 2 + 3 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 others/notes diff --git a/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java b/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java index 151822c0..bf8c76c4 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java +++ b/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java @@ -1,10 +1,15 @@ package com.danielkim.soundrecorder.fragments; +import android.Manifest; import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.SystemClock; +import android.support.v4.app.ActivityCompat; import android.support.v4.app.Fragment; +import android.support.v4.content.ContextCompat; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -28,6 +33,11 @@ * create an instance of this fragment. */ public class RecordFragment extends Fragment { + // Constants. + private static final int REQUEST_DANGEROUS_PERMISSION = 0; + + private boolean marshmallow = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M; + // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER private static final String ARG_POSITION = "position"; private static final String LOG_TAG = RecordFragment.class.getSimpleName(); @@ -86,8 +96,11 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, mRecordButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - onRecord(mStartRecording); - mStartRecording = !mStartRecording; + if (!marshmallow || !mStartRecording) { + record(mStartRecording); + } else { + checkPermissions(); + } } }); @@ -104,6 +117,32 @@ public void onClick(View v) { return recordView; } + private void record(boolean startRecording) { + onRecord(startRecording); + mStartRecording = !startRecording; + } + + // Check dangerous permissions for Android Marshmallow+. + private void checkPermissions() { + // Check permissions. + boolean writePerm = ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; + boolean audioPerm = ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED; + String[] arrPermissions; + if (!writePerm && !audioPerm) { + arrPermissions = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO}; + } else if (!writePerm && audioPerm) { + arrPermissions = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; + } else if (writePerm && !audioPerm) { + arrPermissions = new String[]{Manifest.permission.RECORD_AUDIO}; + } else { + record(true); + return; + } + + // Request permissions. + ActivityCompat.requestPermissions(getActivity(), arrPermissions, REQUEST_DANGEROUS_PERMISSION); + } + // Recording Start/Stop //TODO: recording pause private void onRecord(boolean start){ diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml index 07b371ca..3f1d36d0 100644 --- a/app/src/main/res/values-v21/styles.xml +++ b/app/src/main/res/values-v21/styles.xml @@ -1,9 +1,8 @@ - + + + + From 1363bf127c9663f1674ea1f9d70964da5528ca8a Mon Sep 17 00:00:00 2001 From: iClaude Date: Wed, 20 Sep 2017 14:34:32 +0200 Subject: [PATCH 44/96] add scheduled recording - date and time correctness check --- .../AddScheduledRecordingActivity.java | 39 ++++++++++++++----- .../activities/MainActivity.java | 2 +- .../database/RecordingsContract.java | 3 ++ app/src/main/res/values-it/strings.xml | 3 +- app/src/main/res/values/strings.xml | 3 +- 5 files changed, 37 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/danielkim/soundrecorder/activities/AddScheduledRecordingActivity.java b/app/src/main/java/com/danielkim/soundrecorder/activities/AddScheduledRecordingActivity.java index b8f2b4b8..8b6ffe07 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/activities/AddScheduledRecordingActivity.java +++ b/app/src/main/java/com/danielkim/soundrecorder/activities/AddScheduledRecordingActivity.java @@ -35,6 +35,9 @@ import java.util.GregorianCalendar; import java.util.Locale; +import static com.danielkim.soundrecorder.database.RecordingsContract.MAX_DURATION; +import static com.danielkim.soundrecorder.database.RecordingsContract.MIN_DURATION; + /** * Activity used to add a new scheduled recording. */ @@ -42,6 +45,9 @@ public class AddScheduledRecordingActivity extends AppCompatActivity implements MyOnDateSetListener, MyOnTimeSetListener { public static final String EXTRA_DATE_LONG = "com.danielkim.soundrecorder.activities.EXTRA_DATE_LONG"; public static final String EXTRA_ITEM = "com.danielkim.soundrecorder.activities.EXTRA_ITEM"; + private static final int ERROR_NO_ERROR = -1; + private static final int ERROR_START_AFTER_END = 0; + private static final int ERROR_TIME_PAST = 1; private TextView tvDateStart; private TextView tvDateEnd; @@ -51,7 +57,8 @@ public class AddScheduledRecordingActivity extends AppCompatActivity implements private final DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM); private int yearStart, monthStart, dayStart, hourStart, minuteStart; private int yearEnd, monthEnd, dayEnd, hourEnd, minuteEnd; - private boolean timesCorrect = true; + private int timeErrorCode = ERROR_NO_ERROR; + private final int[] errorMsgs = {R.string.toast_scheduledrecording_timeerror_start_after_end, R.string.toast_scheduledrecording_timeerror_past}; public static Intent makeIntent(Context context, long selectedDate) { @@ -85,7 +92,9 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { long selectedDateLong = getIntent().getLongExtra(EXTRA_DATE_LONG, System.currentTimeMillis()); initVariables(selectedDateLong); + checkDatesAndTimes(); displayDatesAndTimes(); + } // Initialize starting and ending days and times. @@ -151,23 +160,28 @@ public void onTimeSet(long viewId, int hour, int minute) { } private void checkDatesAndTimes() { - if (!datesTimesCorrect()) { - timesCorrect = false; + timeErrorCode = getTimeErrorCode(); + if (timeErrorCode != ERROR_NO_ERROR) { tvDateStart.setTextColor(ContextCompat.getColor(this, R.color.primary_dark)); tvTimeStart.setTextColor(ContextCompat.getColor(this, R.color.primary_dark)); - } else { - timesCorrect = true; tvDateStart.setTextColor(ContextCompat.getColor(this, R.color.primary_text)); tvTimeStart.setTextColor(ContextCompat.getColor(this, R.color.primary_text)); } } - // Dates and times are correct? - private boolean datesTimesCorrect() { + // Dates and times are correct? What kind of error there is? + private int getTimeErrorCode() { Calendar start = new GregorianCalendar(yearStart, monthStart, dayStart, hourStart, minuteStart); Calendar end = new GregorianCalendar(yearEnd, monthEnd, dayEnd, hourEnd, minuteEnd); - return end.after(start); + + if (System.currentTimeMillis() > start.getTimeInMillis()) { + return ERROR_TIME_PAST; + } else if (end.before(start)) { + return ERROR_START_AFTER_END; + } else { + return ERROR_NO_ERROR; + } } @Override @@ -189,10 +203,10 @@ public boolean onOptionsItemSelected(MenuItem item) { } private void addScheduledRecording() { - if (timesCorrect) { + if (timeErrorCode == ERROR_NO_ERROR) { new AddScheduledRecordingsTask().execute(); } else { - Toast.makeText(this, getString(R.string.toast_scheduledrecording_time_uncorrect), Toast.LENGTH_SHORT).show(); + Toast.makeText(this, getString(errorMsgs[timeErrorCode]), Toast.LENGTH_SHORT).show(); } } @@ -202,6 +216,11 @@ private class AddScheduledRecordingsTask extends AsyncTask { protected Long doInBackground(Void... params) { long startLong = new GregorianCalendar(yearStart, monthStart, dayStart, hourStart, minuteStart).getTimeInMillis(); long endLong = new GregorianCalendar(yearEnd, monthEnd, dayEnd, hourEnd, minuteEnd).getTimeInMillis(); + if (endLong - startLong < MIN_DURATION) { + endLong = startLong + MIN_DURATION; // a scheduled recording must be at least 5 minutes + } else if (endLong - startLong > MAX_DURATION) { + endLong = startLong + MAX_DURATION; // a scheduled recording must be at most 3 hours + } return dbHelper.addScheduledRecording(startLong, endLong); } diff --git a/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java b/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java index 92144736..0a3430f9 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java +++ b/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java @@ -57,7 +57,7 @@ public boolean onOptionsItemSelected(MenuItem item) { // as you specify a parent activity in AndroidManifest.xml. // Handle presses on the action bar items switch (item.getItemId()) { - case R.id.action_save: + case R.id.action_licenses: openLicenses(); return true; default: diff --git a/app/src/main/java/com/danielkim/soundrecorder/database/RecordingsContract.java b/app/src/main/java/com/danielkim/soundrecorder/database/RecordingsContract.java index 7c11a92f..46c603c3 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/database/RecordingsContract.java +++ b/app/src/main/java/com/danielkim/soundrecorder/database/RecordingsContract.java @@ -26,6 +26,9 @@ public static class TableScheduledRecording implements BaseColumns { public static final String COLUMN_NAME_END = "end"; // length of the recording in ms } + // Requirements. + public static final int MIN_DURATION = 1000 * 60 * 5; // 5 minutes + public static final int MAX_DURATION = 1000 * 60 * 60 * 3; // 3 hours private RecordingsContract() { } diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 48f8a3e4..3a9680a7 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -14,7 +14,8 @@ Registazione salvata in %1$s eliminato con successo Il file %1$s già esiste. Scegliere un nome del file differente. - L\'ora iniziale della registrazione deve essere precedente all\'ora finale! + L\'ora iniziale della registrazione deve essere precedente all\'ora finale! + Non è possibile pianificare una registrazione nel passato! La registrazione è stata pianificata Errore nell\'inserimento della registrazione pianificata! Errore nell\'eliminazione della registrazione pianificata! diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fc6521e1..5b5b3d3f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -15,7 +15,8 @@ Recording saved to %1$s successfully deleted The file %1$s already exists. Please choose a different file name. - The start time of the recording must be before the end time! + The start time of the recording must be before the end time! + It is not possible to schedule a recording in the past! The scheduled recording was set An error occured while adding the scheduled recording! An error occured while deleting the scheduled recording! From bf709439848f2591418ee8eef0e659b5b8c3c07e Mon Sep 17 00:00:00 2001 From: iClaude Date: Wed, 20 Sep 2017 20:47:01 +0200 Subject: [PATCH 45/96] add scheduled recording - working on error codes and msgs --- .../AddScheduledRecordingActivity.java | 127 ++++++++++++++---- .../ScheduledRecordingsFragment.java | 2 +- .../fragments/TimePickerFragment.java | 20 +-- app/src/main/res/values-it/strings.xml | 5 +- app/src/main/res/values/strings.xml | 5 +- 5 files changed, 118 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/com/danielkim/soundrecorder/activities/AddScheduledRecordingActivity.java b/app/src/main/java/com/danielkim/soundrecorder/activities/AddScheduledRecordingActivity.java index 8b6ffe07..311d176e 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/activities/AddScheduledRecordingActivity.java +++ b/app/src/main/java/com/danielkim/soundrecorder/activities/AddScheduledRecordingActivity.java @@ -9,6 +9,7 @@ import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.content.ContextCompat; import android.support.v7.app.ActionBar; @@ -43,22 +44,35 @@ */ public class AddScheduledRecordingActivity extends AppCompatActivity implements MyOnDateSetListener, MyOnTimeSetListener { - public static final String EXTRA_DATE_LONG = "com.danielkim.soundrecorder.activities.EXTRA_DATE_LONG"; - public static final String EXTRA_ITEM = "com.danielkim.soundrecorder.activities.EXTRA_ITEM"; - private static final int ERROR_NO_ERROR = -1; - private static final int ERROR_START_AFTER_END = 0; - private static final int ERROR_TIME_PAST = 1; + private enum Operation {ADD, EDIT} + + ; + + private interface StatusCodes { + int NO_ERROR = 0; + int ERROR_START_AFTER_END = 1; + int ERROR_TIME_PAST = 2; + int ERROR_ALREADY_SCHEDULED = 3; + int ERROR_SAVING = 4; + } + + private static final String EXTRA_DATE_LONG = "com.danielkim.soundrecorder.activities.EXTRA_DATE_LONG"; + private static final String EXTRA_ITEM = "com.danielkim.soundrecorder.activities.EXTRA_ITEM"; private TextView tvDateStart; private TextView tvDateEnd; private TextView tvTimeStart; private TextView tvTimeEnd; + private Operation operation; + private ScheduledRecordingItem item = null; private final DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM); private int yearStart, monthStart, dayStart, hourStart, minuteStart; private int yearEnd, monthEnd, dayEnd, hourEnd, minuteEnd; - private int timeErrorCode = ERROR_NO_ERROR; - private final int[] errorMsgs = {R.string.toast_scheduledrecording_timeerror_start_after_end, R.string.toast_scheduledrecording_timeerror_past}; + private int statusCode = StatusCodes.NO_ERROR; + private final int[] errorMsgs = {R.string.toast_scheduledrecording_saved, + R.string.toast_scheduledrecording_timeerror_start_after_end, R.string.toast_scheduledrecording_timeerror_past, + R.string.toast_scheduledrecording_timeerror_already_scheduled, R.string.toast_scheduledrecording_saved_error}; public static Intent makeIntent(Context context, long selectedDate) { @@ -81,8 +95,10 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { Toolbar myToolbar = (Toolbar) findViewById(R.id.my_toolbar); setSupportActionBar(myToolbar); ActionBar ab = getSupportActionBar(); - ab.setDisplayShowTitleEnabled(false); // hide the title - ab.setDisplayHomeAsUpEnabled(true); + if (ab != null) { + ab.setDisplayShowTitleEnabled(false); // hide the title + ab.setDisplayHomeAsUpEnabled(true); + } tvDateStart = (TextView) findViewById(R.id.tvDateStart); @@ -91,14 +107,21 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { tvTimeEnd = (TextView) findViewById(R.id.tvTimeEnd); long selectedDateLong = getIntent().getLongExtra(EXTRA_DATE_LONG, System.currentTimeMillis()); - initVariables(selectedDateLong); + item = getIntent().getParcelableExtra(EXTRA_ITEM); + if (item == null) { + initVariables(selectedDateLong); + } else { + initVariables(item); + } checkDatesAndTimes(); displayDatesAndTimes(); } - // Initialize starting and ending days and times. + // Initialize starting and ending days and times (operation = add). private void initVariables(long selectedDateLong) { + operation = Operation.ADD; + Calendar cal = new GregorianCalendar(); cal.setTimeInMillis(selectedDateLong); yearStart = yearEnd = cal.get(Calendar.YEAR); @@ -110,6 +133,26 @@ private void initVariables(long selectedDateLong) { minuteEnd = 0; } + // Initialize starting and ending days and times (operation = edit). + private void initVariables(@NonNull ScheduledRecordingItem item) { + operation = Operation.EDIT; + + Calendar cal = new GregorianCalendar(); + cal.setTimeInMillis(item.getStart()); + yearStart = cal.get(Calendar.YEAR); + monthStart = cal.get(Calendar.MONTH); + dayStart = cal.get(Calendar.DAY_OF_MONTH); + hourStart = cal.get(Calendar.HOUR_OF_DAY); + minuteStart = cal.get(Calendar.MINUTE); + + cal.setTimeInMillis(item.getEnd()); + yearEnd = cal.get(Calendar.YEAR); + monthEnd = cal.get(Calendar.MONTH); + dayEnd = cal.get(Calendar.DAY_OF_MONTH); + hourEnd = cal.get(Calendar.HOUR_OF_DAY); + minuteEnd = cal.get(Calendar.MINUTE); + } + // When dates and times change, display them again. private void displayDatesAndTimes() { tvDateStart.setText(dateFormat.format(new Date(new GregorianCalendar(yearStart, monthStart, dayStart).getTimeInMillis()))); @@ -124,7 +167,17 @@ public void showDatePickerDialog(View view) { } public void showTimePickerDialog(View view) { - DialogFragment timePicker = TimePickerFragment.newInstance(view.getId()); + int hour = 0; + int minute = 0; + if (view.getId() == R.id.tvTimeStart) { + hour = hourStart; + minute = minuteStart; + } else if (view.getId() == R.id.tvTimeEnd) { + hour = hourEnd; + minute = minuteEnd; + } + + DialogFragment timePicker = TimePickerFragment.newInstance(view.getId(), hour, minute); timePicker.show(getFragmentManager(), "timePicker"); } @@ -160,8 +213,8 @@ public void onTimeSet(long viewId, int hour, int minute) { } private void checkDatesAndTimes() { - timeErrorCode = getTimeErrorCode(); - if (timeErrorCode != ERROR_NO_ERROR) { + statusCode = getTimeErrorCode(); + if (statusCode != StatusCodes.NO_ERROR) { tvDateStart.setTextColor(ContextCompat.getColor(this, R.color.primary_dark)); tvTimeStart.setTextColor(ContextCompat.getColor(this, R.color.primary_dark)); } else { @@ -176,11 +229,11 @@ private int getTimeErrorCode() { Calendar end = new GregorianCalendar(yearEnd, monthEnd, dayEnd, hourEnd, minuteEnd); if (System.currentTimeMillis() > start.getTimeInMillis()) { - return ERROR_TIME_PAST; + return StatusCodes.ERROR_TIME_PAST; } else if (end.before(start)) { - return ERROR_START_AFTER_END; + return StatusCodes.ERROR_START_AFTER_END; } else { - return ERROR_NO_ERROR; + return StatusCodes.NO_ERROR; } } @@ -195,25 +248,29 @@ public boolean onCreateOptionsMenu(Menu menu) { public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_save: - addScheduledRecording(); + saveScheduledRecording(); + return true; + case android.R.id.home: + super.onBackPressed(); return true; default: return super.onOptionsItemSelected(item); } } - private void addScheduledRecording() { - if (timeErrorCode == ERROR_NO_ERROR) { - new AddScheduledRecordingsTask().execute(); + private void saveScheduledRecording() { + if (statusCode == StatusCodes.NO_ERROR) { + new SaveScheduledRecordingsTask().execute(); } else { - Toast.makeText(this, getString(errorMsgs[timeErrorCode]), Toast.LENGTH_SHORT).show(); + Toast.makeText(this, getString(errorMsgs[statusCode]), Toast.LENGTH_SHORT).show(); } } - private class AddScheduledRecordingsTask extends AsyncTask { + private class SaveScheduledRecordingsTask extends AsyncTask { private final DBHelper dbHelper = new DBHelper(AddScheduledRecordingActivity.this); - protected Long doInBackground(Void... params) { + protected Integer doInBackground(Void... params) { + int result = statusCode; long startLong = new GregorianCalendar(yearStart, monthStart, dayStart, hourStart, minuteStart).getTimeInMillis(); long endLong = new GregorianCalendar(yearEnd, monthEnd, dayEnd, hourEnd, minuteEnd).getTimeInMillis(); if (endLong - startLong < MIN_DURATION) { @@ -221,16 +278,32 @@ protected Long doInBackground(Void... params) { } else if (endLong - startLong > MAX_DURATION) { endLong = startLong + MAX_DURATION; // a scheduled recording must be at most 3 hours } - return dbHelper.addScheduledRecording(startLong, endLong); + + if (dbHelper.alreadyScheduled(startLong)) { + statusCode = StatusCodes.ERROR_ALREADY_SCHEDULED; + return statusCode; + } + + if (operation == Operation.ADD) { + long id = dbHelper.addScheduledRecording(startLong, endLong); + if (id == -1) { + statusCode = StatusCodes.ERROR_SAVING; + return statusCode; + } + } else { + int updated = dbHelper.updateScheduledRecording(item.getId(), startLong, endLong); + if (updated == 0) { + statusCode + } + } } protected void onPostExecute(Long rowId) { - String msg = rowId == -1 ? getString(R.string.toast_scheduledrecording_added_error) : getString(R.string.toast_scheduledrecording_added); + String msg = rowId == -1 ? getString(R.string.toast_scheduledrecording_saved_error) : getString(R.string.toast_scheduledrecording_saved); Toast.makeText(AddScheduledRecordingActivity.this, msg, Toast.LENGTH_SHORT).show(); if (rowId != -1) setResult(RESULT_OK); finish(); } } - } diff --git a/app/src/main/java/com/danielkim/soundrecorder/fragments/ScheduledRecordingsFragment.java b/app/src/main/java/com/danielkim/soundrecorder/fragments/ScheduledRecordingsFragment.java index 5cd25d4a..85dcc84c 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/fragments/ScheduledRecordingsFragment.java +++ b/app/src/main/java/com/danielkim/soundrecorder/fragments/ScheduledRecordingsFragment.java @@ -218,7 +218,7 @@ public void onClick(View view) { public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); - if (requestCode == ADD_SCHEDULED_RECORDING && resultCode == Activity.RESULT_OK) { + if ((requestCode == ADD_SCHEDULED_RECORDING || requestCode == EDIT_SCHEDULED_RECORDING) && resultCode == Activity.RESULT_OK) { new GetScheduledRecordingsTask().execute(); } } diff --git a/app/src/main/java/com/danielkim/soundrecorder/fragments/TimePickerFragment.java b/app/src/main/java/com/danielkim/soundrecorder/fragments/TimePickerFragment.java index 852caf74..19771b52 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/fragments/TimePickerFragment.java +++ b/app/src/main/java/com/danielkim/soundrecorder/fragments/TimePickerFragment.java @@ -8,8 +8,6 @@ import android.text.format.DateFormat; import android.widget.TimePicker; -import java.util.Calendar; - /** * Shows a dialog to pick a time (hour and minute). * Communicates the time selected through an interface. @@ -18,14 +16,19 @@ */ public class TimePickerFragment extends DialogFragment implements TimePickerDialog.OnTimeSetListener { - private static final String VIEW_ID = "view_id"; + private static final String ARG_VIEW_ID = "ARG_VIEW_ID"; + private static final String ARG_HOUR = "ARG_HOUR"; + private static final String ARG_MINUTE = "ARG_MINUTE"; + private MyOnTimeSetListener listener; - public static TimePickerFragment newInstance(long viewId) { + public static TimePickerFragment newInstance(long viewId, int hour, int minute) { TimePickerFragment f = new TimePickerFragment(); Bundle bundle = new Bundle(); - bundle.putLong(VIEW_ID, viewId); + bundle.putLong(ARG_VIEW_ID, viewId); + bundle.putInt(ARG_HOUR, hour); + bundle.putInt(ARG_MINUTE, minute); f.setArguments(bundle); return f; @@ -33,9 +36,8 @@ public static TimePickerFragment newInstance(long viewId) { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - final Calendar c = Calendar.getInstance(); - int hour = c.get(Calendar.HOUR_OF_DAY); - int minute = c.get(Calendar.MINUTE); + int hour = getArguments().getInt(ARG_HOUR); + int minute = getArguments().getInt(ARG_MINUTE); return new TimePickerDialog(getActivity(), this, hour, minute, DateFormat.is24HourFormat(getActivity())); } @@ -54,7 +56,7 @@ public void onAttach(Context context) { @Override public void onTimeSet(TimePicker timePicker, int hourOfDay, int minute) { if (listener != null) { - listener.onTimeSet(getArguments().getLong(VIEW_ID, 0), hourOfDay, minute); + listener.onTimeSet(getArguments().getLong(ARG_VIEW_ID, 0), hourOfDay, minute); } } diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 3a9680a7..1aa0abf1 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -16,8 +16,9 @@ Il file %1$s già esiste. Scegliere un nome del file differente. L\'ora iniziale della registrazione deve essere precedente all\'ora finale! Non è possibile pianificare una registrazione nel passato! - La registrazione è stata pianificata - Errore nell\'inserimento della registrazione pianificata! + La registrazione pianificata è stata salvata + Errore nel salvataggio della registrazione pianificata! + Un\'altra registrazione è già pianificata per l\'orario selezionato! Errore nell\'eliminazione della registrazione pianificata! Elemento cancellato con successo diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5b5b3d3f..6f256273 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -17,8 +17,9 @@ The file %1$s already exists. Please choose a different file name. The start time of the recording must be before the end time! It is not possible to schedule a recording in the past! - The scheduled recording was set - An error occured while adding the scheduled recording! + Another recording has already been scheduled for that time! + The scheduled recording was saved + An error occured while saving the scheduled recording! An error occured while deleting the scheduled recording! Item successfully deleted From 8a3c40c6662a3ebe4c27f0c1038a5ddcc555b682 Mon Sep 17 00:00:00 2001 From: iClaude Date: Fri, 22 Sep 2017 14:15:17 +0200 Subject: [PATCH 46/96] add scheduled recording - activity completed --- .../AddScheduledRecordingActivity.java | 34 +++++++++---------- .../ScheduledRecordingsFragment.java | 24 +++---------- ...cheduledRecordingsFragmentItemAdapter.java | 26 ++++++-------- 3 files changed, 31 insertions(+), 53 deletions(-) diff --git a/app/src/main/java/com/danielkim/soundrecorder/activities/AddScheduledRecordingActivity.java b/app/src/main/java/com/danielkim/soundrecorder/activities/AddScheduledRecordingActivity.java index 311d176e..5b37775b 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/activities/AddScheduledRecordingActivity.java +++ b/app/src/main/java/com/danielkim/soundrecorder/activities/AddScheduledRecordingActivity.java @@ -46,8 +46,6 @@ public class AddScheduledRecordingActivity extends AppCompatActivity implements MyOnDateSetListener, MyOnTimeSetListener { private enum Operation {ADD, EDIT} - ; - private interface StatusCodes { int NO_ERROR = 0; int ERROR_START_AFTER_END = 1; @@ -70,7 +68,7 @@ private interface StatusCodes { private int yearStart, monthStart, dayStart, hourStart, minuteStart; private int yearEnd, monthEnd, dayEnd, hourEnd, minuteEnd; private int statusCode = StatusCodes.NO_ERROR; - private final int[] errorMsgs = {R.string.toast_scheduledrecording_saved, + private final int[] toastMsgs = {R.string.toast_scheduledrecording_saved, R.string.toast_scheduledrecording_timeerror_start_after_end, R.string.toast_scheduledrecording_timeerror_past, R.string.toast_scheduledrecording_timeerror_already_scheduled, R.string.toast_scheduledrecording_saved_error}; @@ -262,15 +260,14 @@ private void saveScheduledRecording() { if (statusCode == StatusCodes.NO_ERROR) { new SaveScheduledRecordingsTask().execute(); } else { - Toast.makeText(this, getString(errorMsgs[statusCode]), Toast.LENGTH_SHORT).show(); + Toast.makeText(this, getString(toastMsgs[statusCode]), Toast.LENGTH_SHORT).show(); } } - private class SaveScheduledRecordingsTask extends AsyncTask { + private class SaveScheduledRecordingsTask extends AsyncTask { private final DBHelper dbHelper = new DBHelper(AddScheduledRecordingActivity.this); protected Integer doInBackground(Void... params) { - int result = statusCode; long startLong = new GregorianCalendar(yearStart, monthStart, dayStart, hourStart, minuteStart).getTimeInMillis(); long endLong = new GregorianCalendar(yearEnd, monthEnd, dayEnd, hourEnd, minuteEnd).getTimeInMillis(); if (endLong - startLong < MIN_DURATION) { @@ -279,31 +276,32 @@ protected Integer doInBackground(Void... params) { endLong = startLong + MAX_DURATION; // a scheduled recording must be at most 3 hours } - if (dbHelper.alreadyScheduled(startLong)) { - statusCode = StatusCodes.ERROR_ALREADY_SCHEDULED; - return statusCode; - } - if (operation == Operation.ADD) { + if (dbHelper.alreadyScheduled(startLong)) { + statusCode = StatusCodes.ERROR_ALREADY_SCHEDULED; + return statusCode; + } + long id = dbHelper.addScheduledRecording(startLong, endLong); if (id == -1) { statusCode = StatusCodes.ERROR_SAVING; - return statusCode; } } else { int updated = dbHelper.updateScheduledRecording(item.getId(), startLong, endLong); if (updated == 0) { - statusCode + statusCode = StatusCodes.ERROR_SAVING; } } + + return statusCode; } - protected void onPostExecute(Long rowId) { - String msg = rowId == -1 ? getString(R.string.toast_scheduledrecording_saved_error) : getString(R.string.toast_scheduledrecording_saved); - Toast.makeText(AddScheduledRecordingActivity.this, msg, Toast.LENGTH_SHORT).show(); - if (rowId != -1) + protected void onPostExecute(Integer result) { + Toast.makeText(AddScheduledRecordingActivity.this, toastMsgs[result], Toast.LENGTH_SHORT).show(); + if (result == StatusCodes.NO_ERROR) { setResult(RESULT_OK); - finish(); + finish(); + } } } } diff --git a/app/src/main/java/com/danielkim/soundrecorder/fragments/ScheduledRecordingsFragment.java b/app/src/main/java/com/danielkim/soundrecorder/fragments/ScheduledRecordingsFragment.java index 85dcc84c..fca2d2aa 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/fragments/ScheduledRecordingsFragment.java +++ b/app/src/main/java/com/danielkim/soundrecorder/fragments/ScheduledRecordingsFragment.java @@ -6,7 +6,6 @@ import android.app.Activity; import android.app.AlertDialog; -import android.content.DialogInterface; import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; @@ -49,7 +48,6 @@ public class ScheduledRecordingsFragment extends Fragment implements ScheduledRe private static final String ARG_POSITION = "position"; private static final int ADD_SCHEDULED_RECORDING = 0; private static final int EDIT_SCHEDULED_RECORDING = 1; - private static final String TAG = "SRFragment"; private CompactCalendarView calendarView; private TextView tvMonth; @@ -168,19 +166,10 @@ public void onItemLongClick(ScheduledRecordingItem item) { builder.setTitle(getString(R.string.dialog_title_delete)); builder.setMessage(R.string.dialog_text_delete_generic); builder.setPositiveButton(R.string.dialog_action_ok, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - new DeleteItemTask().execute(item.getId()); - } - }); + (dialogInterface, i) -> new DeleteItemTask().execute(item.getId())); builder.setCancelable(true); builder.setNegativeButton(getString(R.string.dialog_action_cancel), - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - dialog.cancel(); - } - }); + (dialog, id) -> dialog.cancel()); AlertDialog alert = builder.create(); alert.show(); @@ -205,12 +194,9 @@ protected void onPostExecute(Integer result) { } // Click listener of the button to add a new scheduled recording. - private final View.OnClickListener addScheduledRecordingListener = new View.OnClickListener() { - @Override - public void onClick(View view) { - Intent intent = AddScheduledRecordingActivity.makeIntent(getActivity(), selectedDate.getTime()); - startActivityForResult(intent, ADD_SCHEDULED_RECORDING); - } + private final View.OnClickListener addScheduledRecordingListener = view -> { + Intent intent = AddScheduledRecordingActivity.makeIntent(getActivity(), selectedDate.getTime()); + startActivityForResult(intent, ADD_SCHEDULED_RECORDING); }; // After a new scheduled recording has been added, get all the recordings and refresh the layout. diff --git a/app/src/main/java/com/danielkim/soundrecorder/fragments/ScheduledRecordingsFragmentItemAdapter.java b/app/src/main/java/com/danielkim/soundrecorder/fragments/ScheduledRecordingsFragmentItemAdapter.java index 6b8a9189..4ae81e81 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/fragments/ScheduledRecordingsFragmentItemAdapter.java +++ b/app/src/main/java/com/danielkim/soundrecorder/fragments/ScheduledRecordingsFragmentItemAdapter.java @@ -45,25 +45,19 @@ public ItemViewHolder(View v) { tvEnd = (TextView) v.findViewById(R.id.tvEnd); tvColor = (TextView) v.findViewById(R.id.tvColor); - v.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - int pos = recyclerView.getChildAdapterPosition(view); - if (pos >= 0 && pos < getItemCount()) { - listener.onItemClick(items.get(pos)); - } + v.setOnClickListener(view -> { + int pos = recyclerView.getChildAdapterPosition(view); + if (pos >= 0 && pos < getItemCount()) { + listener.onItemClick(items.get(pos)); } }); - v.setOnLongClickListener(new View.OnLongClickListener() { - @Override - public boolean onLongClick(View view) { - int pos = recyclerView.getChildAdapterPosition(view); - if (pos >= 0 && pos < getItemCount()) { - listener.onItemLongClick(items.get(pos)); - } - return true; + v.setOnLongClickListener(view -> { + int pos = recyclerView.getChildAdapterPosition(view); + if (pos >= 0 && pos < getItemCount()) { + listener.onItemLongClick(items.get(pos)); } + return true; }); } } @@ -71,7 +65,7 @@ public boolean onLongClick(View view) { // Adapter. private List items; private final MyOnItemClickListener listener; - private RecyclerView recyclerView; + private final RecyclerView recyclerView; private final int[] colors = {Color.argb(255, 255, 193, 7), Color.argb(255, 244, 67, 54), Color.argb(255, 99, 233, 112), Color.argb(255, 7, 168, 255), Color.argb(255, 255, 7, 251), From 9d70550ab73a3a89b674a7d9d8eda3e4f3d5e068 Mon Sep 17 00:00:00 2001 From: iClaude Date: Fri, 22 Sep 2017 15:51:13 +0200 Subject: [PATCH 47/96] scheduled recording service - working on bugs --- .../soundrecorder/RecordingService.java | 37 +++++++++++++++++-- .../AddScheduledRecordingActivity.java | 2 + app/src/main/res/values-it/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 4 files changed, 37 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java b/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java index 1819229d..fde8df19 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java +++ b/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java @@ -23,8 +23,11 @@ import java.util.Timer; import java.util.TimerTask; +import static android.media.MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED; + /** * Created by Daniel on 12/28/2014. + * Edited by iClaude. */ public class RecordingService extends Service { @@ -47,6 +50,8 @@ public class RecordingService extends Service { private Timer mTimer = null; private TimerTask mIncrementTimerTask = null; + private ScheduledRecordingItem item = null; + /* Static factory method used to create an Intent to start this Service for @@ -75,7 +80,7 @@ public void onCreate() { @Override public int onStartCommand(Intent intent, int flags, int startId) { - ScheduledRecordingItem item = intent.getParcelableExtra(EXTRA_ITEM); + item = intent.getParcelableExtra(EXTRA_ITEM); int duration = item == null ? 0 : (int) (item.getEnd() - item.getStart()); // is this a scheduled recording? startRecording(duration); @@ -102,6 +107,15 @@ public void startRecording(int duration) { mRecorder.setMaxDuration(duration); // if this is a scheduled recording, set the max duration, after which the Service is stopped mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); mRecorder.setAudioChannels(1); + mRecorder.setOnInfoListener(new MediaRecorder.OnInfoListener() { + @Override + // Called only if a max duration has been set (scheduled recordings). + public void onInfo(MediaRecorder mediaRecorder, int what, int extra) { + if (what == MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) { + stopScheduledRecording(); + } + } + }); try { mRecorder.prepare(); @@ -109,7 +123,7 @@ public void startRecording(int duration) { mStartingTimeMillis = System.currentTimeMillis(); //startTimer(); - //startForeground(1, createNotification()); + startForeground(1, createNotification()); } catch (IOException e) { Log.e(LOG_TAG, "prepare() failed"); @@ -152,6 +166,22 @@ public void stopRecording() { } catch (Exception e){ Log.e(LOG_TAG, "exception", e); } + + stopForeground(true); + stopSelf(); + } + + // Specific to scheduled recordings. + private void stopScheduledRecording() { + // Remove scheduled recording from database. + DBHelper dbHelper = new DBHelper(this); + dbHelper.removeScheduledRecording(item.getId()); + + // Schedule next recording. + startService(ScheduledRecordingService.makeIntent(this, false)); + + // Save recording file to database. + stopRecording(); } private void startTimer() { @@ -169,13 +199,12 @@ public void run() { mTimer.scheduleAtFixedRate(mIncrementTimerTask, 1000, 1000); } - //TODO: private Notification createNotification() { NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getApplicationContext()) .setSmallIcon(R.drawable.ic_mic_white_36dp) .setContentTitle(getString(R.string.notification_recording)) - .setContentText(mTimerFormat.format(mElapsedSeconds * 1000)) + .setContentText(getString(R.string.notification_recording_text)) .setOngoing(true); mBuilder.setContentIntent(PendingIntent.getActivities(getApplicationContext(), 0, diff --git a/app/src/main/java/com/danielkim/soundrecorder/activities/AddScheduledRecordingActivity.java b/app/src/main/java/com/danielkim/soundrecorder/activities/AddScheduledRecordingActivity.java index 5b37775b..003a07bc 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/activities/AddScheduledRecordingActivity.java +++ b/app/src/main/java/com/danielkim/soundrecorder/activities/AddScheduledRecordingActivity.java @@ -24,6 +24,7 @@ import com.danielkim.soundrecorder.R; import com.danielkim.soundrecorder.ScheduledRecordingItem; +import com.danielkim.soundrecorder.ScheduledRecordingService; import com.danielkim.soundrecorder.database.DBHelper; import com.danielkim.soundrecorder.fragments.DatePickerFragment; import com.danielkim.soundrecorder.fragments.DatePickerFragment.MyOnDateSetListener; @@ -300,6 +301,7 @@ protected void onPostExecute(Integer result) { Toast.makeText(AddScheduledRecordingActivity.this, toastMsgs[result], Toast.LENGTH_SHORT).show(); if (result == StatusCodes.NO_ERROR) { setResult(RESULT_OK); + startService(ScheduledRecordingService.makeIntent(AddScheduledRecordingActivity.this, false)); finish(); } } diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 1aa0abf1..5e582da7 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -24,6 +24,7 @@ Registrazione... + SoundRecorder sta registrando Conferma cancellazione diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6f256273..e90b2dc0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -26,6 +26,7 @@ Recording... + SoundRecorder is currently recording Confirm Delete... From 6669f367d944d520e61abb1a5619fe90afc81b05 Mon Sep 17 00:00:00 2001 From: iClaude Date: Fri, 22 Sep 2017 20:54:16 +0200 Subject: [PATCH 48/96] scheduled recording service - working on bugs 2 --- .../soundrecorder/RecordingService.java | 20 +++++++++----- .../ScheduledRecordingService.java | 2 +- .../ScheduledRecordingsFragment.java | 27 +++++++++++++++---- ...cheduledRecordingsFragmentItemAdapter.java | 24 ++++++++++------- 4 files changed, 51 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java b/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java index fde8df19..72500cc2 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java +++ b/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java @@ -33,6 +33,7 @@ public class RecordingService extends Service { private static final String LOG_TAG = "RecordingService"; private static final String EXTRA_ITEM = "com.danielkim.soundrecorder.ITEM"; + private static final int ONGOING_NOTIFICATION = 1; private String mFileName = null; private String mFilePath = null; @@ -80,8 +81,18 @@ public void onCreate() { @Override public int onStartCommand(Intent intent, int flags, int startId) { + startForeground(ONGOING_NOTIFICATION, createNotification()); + item = intent.getParcelableExtra(EXTRA_ITEM); - int duration = item == null ? 0 : (int) (item.getEnd() - item.getStart()); // is this a scheduled recording? + int duration; // is this a scheduled recording? + if (item == null) { + duration = 0; + } else { + duration = (int) (item.getEnd() - item.getStart()); + // Schedule next recording. + startService(ScheduledRecordingService.makeIntent(this, false)); + } + startRecording(duration); return START_STICKY; @@ -123,7 +134,6 @@ public void onInfo(MediaRecorder mediaRecorder, int what, int extra) { mStartingTimeMillis = System.currentTimeMillis(); //startTimer(); - startForeground(1, createNotification()); } catch (IOException e) { Log.e(LOG_TAG, "prepare() failed"); @@ -174,11 +184,7 @@ public void stopRecording() { // Specific to scheduled recordings. private void stopScheduledRecording() { // Remove scheduled recording from database. - DBHelper dbHelper = new DBHelper(this); - dbHelper.removeScheduledRecording(item.getId()); - - // Schedule next recording. - startService(ScheduledRecordingService.makeIntent(this, false)); + mDatabase.removeScheduledRecording(item.getId()); // Save recording file to database. stopRecording(); diff --git a/app/src/main/java/com/danielkim/soundrecorder/ScheduledRecordingService.java b/app/src/main/java/com/danielkim/soundrecorder/ScheduledRecordingService.java index b6b3984e..e030d117 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/ScheduledRecordingService.java +++ b/app/src/main/java/com/danielkim/soundrecorder/ScheduledRecordingService.java @@ -37,7 +37,7 @@ public class ScheduledRecordingService extends Service implements Handler.Callba @VisibleForTesting(otherwise = VisibleForTesting.NONE) public static boolean wakeful; @VisibleForTesting(otherwise = VisibleForTesting.NONE) - private LocalBinder localBinder = new LocalBinder(); + private final LocalBinder localBinder = new LocalBinder(); /* Static factory method used to create an Intent to start this Service. diff --git a/app/src/main/java/com/danielkim/soundrecorder/fragments/ScheduledRecordingsFragment.java b/app/src/main/java/com/danielkim/soundrecorder/fragments/ScheduledRecordingsFragment.java index fca2d2aa..acc5032e 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/fragments/ScheduledRecordingsFragment.java +++ b/app/src/main/java/com/danielkim/soundrecorder/fragments/ScheduledRecordingsFragment.java @@ -6,6 +6,7 @@ import android.app.Activity; import android.app.AlertDialog; +import android.content.DialogInterface; import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; @@ -22,6 +23,7 @@ import com.danielkim.soundrecorder.R; import com.danielkim.soundrecorder.ScheduledRecordingItem; +import com.danielkim.soundrecorder.ScheduledRecordingService; import com.danielkim.soundrecorder.activities.AddScheduledRecordingActivity; import com.danielkim.soundrecorder.database.DBHelper; import com.github.sundeepk.compactcalendarview.CompactCalendarView; @@ -48,6 +50,7 @@ public class ScheduledRecordingsFragment extends Fragment implements ScheduledRe private static final String ARG_POSITION = "position"; private static final int ADD_SCHEDULED_RECORDING = 0; private static final int EDIT_SCHEDULED_RECORDING = 1; + private static final String TAG = "SRFragment"; private CompactCalendarView calendarView; private TextView tvMonth; @@ -166,10 +169,19 @@ public void onItemLongClick(ScheduledRecordingItem item) { builder.setTitle(getString(R.string.dialog_title_delete)); builder.setMessage(R.string.dialog_text_delete_generic); builder.setPositiveButton(R.string.dialog_action_ok, - (dialogInterface, i) -> new DeleteItemTask().execute(item.getId())); + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + new DeleteItemTask().execute(item.getId()); + } + }); builder.setCancelable(true); builder.setNegativeButton(getString(R.string.dialog_action_cancel), - (dialog, id) -> dialog.cancel()); + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); AlertDialog alert = builder.create(); alert.show(); @@ -187,6 +199,8 @@ protected void onPostExecute(Integer result) { if (result > 0) { Toast.makeText(getActivity(), getString(R.string.toast_scheduledrecording_deleted), Toast.LENGTH_SHORT).show(); new GetScheduledRecordingsTask().execute(); + getActivity().startService(ScheduledRecordingService.makeIntent(getActivity(), false)); + } else { Toast.makeText(getActivity(), getString(R.string.toast_scheduledrecording_deleted_error), Toast.LENGTH_SHORT).show(); } @@ -194,9 +208,12 @@ protected void onPostExecute(Integer result) { } // Click listener of the button to add a new scheduled recording. - private final View.OnClickListener addScheduledRecordingListener = view -> { - Intent intent = AddScheduledRecordingActivity.makeIntent(getActivity(), selectedDate.getTime()); - startActivityForResult(intent, ADD_SCHEDULED_RECORDING); + private final View.OnClickListener addScheduledRecordingListener = new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = AddScheduledRecordingActivity.makeIntent(getActivity(), selectedDate.getTime()); + startActivityForResult(intent, ADD_SCHEDULED_RECORDING); + } }; // After a new scheduled recording has been added, get all the recordings and refresh the layout. diff --git a/app/src/main/java/com/danielkim/soundrecorder/fragments/ScheduledRecordingsFragmentItemAdapter.java b/app/src/main/java/com/danielkim/soundrecorder/fragments/ScheduledRecordingsFragmentItemAdapter.java index 4ae81e81..ce11f991 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/fragments/ScheduledRecordingsFragmentItemAdapter.java +++ b/app/src/main/java/com/danielkim/soundrecorder/fragments/ScheduledRecordingsFragmentItemAdapter.java @@ -45,19 +45,25 @@ public ItemViewHolder(View v) { tvEnd = (TextView) v.findViewById(R.id.tvEnd); tvColor = (TextView) v.findViewById(R.id.tvColor); - v.setOnClickListener(view -> { - int pos = recyclerView.getChildAdapterPosition(view); - if (pos >= 0 && pos < getItemCount()) { - listener.onItemClick(items.get(pos)); + v.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + int pos = recyclerView.getChildAdapterPosition(view); + if (pos >= 0 && pos < getItemCount()) { + listener.onItemClick(items.get(pos)); + } } }); - v.setOnLongClickListener(view -> { - int pos = recyclerView.getChildAdapterPosition(view); - if (pos >= 0 && pos < getItemCount()) { - listener.onItemLongClick(items.get(pos)); + v.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View view) { + int pos = recyclerView.getChildAdapterPosition(view); + if (pos >= 0 && pos < getItemCount()) { + listener.onItemLongClick(items.get(pos)); + } + return true; } - return true; }); } } From fa43cd97b8689e5123d911e038642f267386d217 Mon Sep 17 00:00:00 2001 From: iClaude Date: Fri, 29 Sep 2017 16:14:09 +0200 Subject: [PATCH 49/96] started Service changed to bound Service --- app/build.gradle | 15 +- app/src/main/AndroidManifest.xml | 15 +- .../soundrecorder/RecordingService2.java | 290 ++++++++++++++++++ .../activities/MainActivity.java | 126 +++++++- .../fragments/RecordFragment.java | 172 ++++++----- .../res/drawable-nodpi/example_picture.png | Bin 0 -> 1885 bytes app/src/main/res/layout/fragment_record.xml | 12 +- app/src/main/res/values-it/strings.xml | 2 +- app/src/main/res/values/strings.xml | 17 +- 9 files changed, 550 insertions(+), 99 deletions(-) create mode 100644 app/src/main/java/com/danielkim/soundrecorder/RecordingService2.java create mode 100644 app/src/main/res/drawable-nodpi/example_picture.png diff --git a/app/build.gradle b/app/build.gradle index 786ca3c3..555e877c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,7 +27,7 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } - lintOptions{ + lintOptions { disable 'MissingTranslation' } @@ -51,13 +51,8 @@ dependencies { compile "com.android.support:appcompat-v7:$libraryVersion" compile "com.android.support:cardview-v7:$libraryVersion" compile "com.android.support:recyclerview-v7:$libraryVersion" - compile 'com.android.support.constraint:constraint-layout:1.0.2' - compile 'com.melnykov:floatingactionbutton:1.1.0' - compile 'com.jpardogo.materialtabstrip:library:1.0.6' - compile 'com.github.sundeepk:compact-calendar-view:2.0.2.2' // Testing. - compile 'com.android.support:support-annotations:24.2.0' androidTestCompile('com.android.support.test:runner:0.5') { exclude module: 'support-annotations' } @@ -65,6 +60,12 @@ dependencies { androidTestCompile('com.android.support.test:rules:0.5') { exclude module: 'support-annotations' } + compile 'com.android.support.constraint:constraint-layout:1.0.2' + compile 'com.melnykov:floatingactionbutton:1.1.0' + compile 'com.jpardogo.materialtabstrip:library:1.0.6' + compile 'com.github.sundeepk:compact-calendar-view:2.0.2.2' + compile 'com.android.support:support-annotations:24.2.0' compile 'junit:junit:4.12' - testCompile "org.robolectric:robolectric:3.3.2" + compile 'com.android.support:support-v4:25.3.1' + testCompile 'org.robolectric:robolectric:3.3.2' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5431f421..68cd0c91 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,13 +6,13 @@ + - - - + android:parentActivityName=".activities.MainActivity" + android:screenOrientation="portrait" /> - + + android:exported="false" /> + + + \ No newline at end of file diff --git a/app/src/main/java/com/danielkim/soundrecorder/RecordingService2.java b/app/src/main/java/com/danielkim/soundrecorder/RecordingService2.java new file mode 100644 index 00000000..5ff48041 --- /dev/null +++ b/app/src/main/java/com/danielkim/soundrecorder/RecordingService2.java @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2017. This code was written by iClaude. All rights reserved. + */ + +package com.danielkim.soundrecorder; + + +import android.app.Notification; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.media.MediaRecorder; +import android.os.Binder; +import android.os.Environment; +import android.os.IBinder; +import android.support.v4.app.NotificationCompat; +import android.util.Log; +import android.widget.Toast; + +import com.danielkim.soundrecorder.activities.MainActivity; +import com.danielkim.soundrecorder.database.DBHelper; + +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Locale; +import java.util.Timer; +import java.util.TimerTask; + +import static android.media.MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED; + +/** + * Created by iClaude on 25/09/2017. + * Service used to record audio. This class implements an hybrid Service (bound and started + * Service). + * Compared with the original Service, this class adds 2 new features: + * 1) record scheduled recordings + * 2) bound Service features to connect this Service to an Activity + */ + +public class RecordingService2 extends Service { + private static final String TAG = "SCHEDULED_RECORDER_TAG"; + private static final String EXTRA_ITEM = "com.danielkim.soundrecorder.ITEM"; + private static final int ONGOING_NOTIFICATION = 1; + + private boolean isManualRecording = true; + private String mFileName = null; + private String mFilePath = null; + private MediaRecorder mRecorder = null; + private DBHelper mDatabase; + private long mStartingTimeMillis = 0; + private long mElapsedMillis = 0; + private int mElapsedSeconds = 0; + + //private ScheduledRecordingListener scheduledRecordingListener = null; + private static final SimpleDateFormat mTimerFormat = new SimpleDateFormat("mm:ss", Locale.getDefault()); + private Timer mTimer = null; + private TimerTask mIncrementTimerTask = null; + + private final IBinder myBinder = new LocalBinder(); + private ScheduledRecordingItem scheduledRecordingItem = null; + private boolean isRecording = false; + + + /* + Static factory method used to create an Intent to start this Service for a normal + recording. + */ + public static Intent makeIntent(Context context) { + Intent intent = new Intent(context, RecordingService2.class); + return intent; + } + + /* + Static factory method used to create an Intent to start this Service for + a scheduled recording. + */ + public static Intent makeIntent(Context context, ScheduledRecordingItem item) { + Intent intent = new Intent(context, RecordingService2.class); + intent.putExtra(EXTRA_ITEM, item); + return intent; + } + + /* + The following code implements a bound Service used to connect this Service to an Activity. + */ + + public class LocalBinder extends Binder { + public RecordingService2 getService() { + return RecordingService2.this; + } + } + + @Override + public IBinder onBind(Intent intent) { + return myBinder; + } + + /* + Interface used to communicate the seconds of the current recording to the connected Activity. + */ + public interface OnTimerChangedListener { + void onTimerChanged(int seconds); + } + + private OnTimerChangedListener onTimerChangedListener = null; + + public void setOnTimerChangedListener(OnTimerChangedListener onTimerChangedListener) { + this.onTimerChangedListener = onTimerChangedListener; + } + +/* *//* + Interface used to communicate the start/stop of a scheduled recording to the connected Activity. + *//* + public interface ScheduledRecordingListener { + void onScheduledRecordingStart(); + void onScheduledRecordingStop(); + } + + public void setScheduledRecordingListener(ScheduledRecordingListener listener) { + this.scheduledRecordingListener = listener; + }*/ + + + /* + The following code implements a started Service. + */ + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Log.d(TAG, "Service - onStartCommand"); + scheduledRecordingItem = intent.getParcelableExtra(EXTRA_ITEM); + + int duration = 0; + if (scheduledRecordingItem != null) { // automatic scheduled recording + isManualRecording = false; + duration = (int) (scheduledRecordingItem.getEnd() - scheduledRecordingItem.getStart()); + // Schedule next recording. + startService(ScheduledRecordingService.makeIntent(this, false)); + startRecording(duration); + } + + return START_NOT_STICKY; + } + + // Specific to scheduled recordings. + private void stopScheduledRecording() { + // Remove scheduled recording from database. + mDatabase.removeScheduledRecording(scheduledRecordingItem.getId()); + + // Save recording file to database. + stopRecording(); + stopSelf(); + /* if(scheduledRecordingListener != null) + scheduledRecordingListener.onScheduledRecordingStop();*/ + } + + /* + The following code is shared by both started and bound Service. + */ + + @Override + public void onCreate() { + Log.d(TAG, "Service - onCreate"); + super.onCreate(); + mDatabase = new DBHelper(getApplicationContext()); + } + + @Override + public void onDestroy() { + Log.d(TAG, "Service - onDestroy"); + super.onDestroy(); + if (mRecorder != null) { + stopRecording(); + } + + if (onTimerChangedListener != null) onTimerChangedListener = null; + } + + public void startRecording(int duration) { + Log.d(TAG, "Service - startRecording"); + startForeground(ONGOING_NOTIFICATION, createNotification()); + + setFileNameAndPath(); + mRecorder = new MediaRecorder(); + mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); + mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); + mRecorder.setOutputFile(mFilePath); + mRecorder.setMaxDuration(duration); // if this is a scheduled recording, set the max duration, after which the Service is stopped + mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); + mRecorder.setAudioChannels(1); + mRecorder.setOnInfoListener(new MediaRecorder.OnInfoListener() { + @Override + // Called only if a max duration has been set (scheduled recordings). + public void onInfo(MediaRecorder mediaRecorder, int what, int extra) { + if (what == MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) { + stopScheduledRecording(); + } + } + }); + + try { + mRecorder.prepare(); + mRecorder.start(); + mStartingTimeMillis = System.currentTimeMillis(); + isRecording = true; + + startTimer(); +/* if(duration > 0) { + if(scheduledRecordingListener != null) + scheduledRecordingListener.onScheduledRecordingStart(); + }*/ + } catch (IOException e) { + Log.e(TAG, "prepare() failed"); + } + } + + public void setFileNameAndPath() { + int count = 0; + File f; + + do { + count++; + + mFileName = getString(R.string.default_file_name) + + " #" + (mDatabase.getSavedRecordingsCount() + count) + ".mp4"; + mFilePath = Environment.getExternalStorageDirectory().getAbsolutePath(); + mFilePath += "/SoundRecorder/" + mFileName; + + f = new File(mFilePath); + } while (f.exists() && !f.isDirectory()); + } + + public void stopRecording() { + Log.d(TAG, "Service - stopRecording"); + mRecorder.stop(); + mElapsedMillis = (System.currentTimeMillis() - mStartingTimeMillis); + mRecorder.release(); + isRecording = false; + Toast.makeText(this, getString(R.string.toast_recording_finish) + " " + mFilePath, Toast.LENGTH_LONG).show(); + + //remove notification + if (mIncrementTimerTask != null) { + mIncrementTimerTask.cancel(); + mIncrementTimerTask = null; + } + + mRecorder = null; + try { + mDatabase.addRecording(mFileName, mFilePath, mElapsedMillis); + } catch (Exception e) { + Log.e(TAG, "exception", e); + } + + stopForeground(true); + } + + private void startTimer() { + mTimer = new Timer(); + mElapsedSeconds = 0; + mIncrementTimerTask = new TimerTask() { + @Override + public void run() { + mElapsedSeconds++; + if (onTimerChangedListener != null) { + onTimerChangedListener.onTimerChanged(mElapsedSeconds); + } + } + }; + mTimer.scheduleAtFixedRate(mIncrementTimerTask, 1000, 1000); + } + + private Notification createNotification() { + NotificationCompat.Builder mBuilder = + new NotificationCompat.Builder(getApplicationContext()) + .setSmallIcon(R.drawable.ic_mic_white_36dp) + .setContentTitle(getString(R.string.notification_recording)) + .setContentText(getString(R.string.notification_recording_text)) + .setOngoing(true); + + mBuilder.setContentIntent(PendingIntent.getActivities(getApplicationContext(), 0, + new Intent[]{new Intent(getApplicationContext(), MainActivity.class)}, 0)); + + return mBuilder.build(); + } + + public boolean isRecording() { + return isRecording; + } +} diff --git a/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java b/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java index 0a3430f9..f7c7e84d 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java +++ b/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java @@ -1,29 +1,39 @@ package com.danielkim.soundrecorder.activities; +import android.content.ComponentName; +import android.content.ServiceConnection; import android.os.Bundle; +import android.os.IBinder; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.view.ViewPager; import android.support.v7.app.ActionBarActivity; import android.support.v7.widget.Toolbar; +import android.util.Log; import android.view.Menu; import android.view.MenuItem; import com.astuetz.PagerSlidingTabStrip; import com.danielkim.soundrecorder.R; +import com.danielkim.soundrecorder.RecordingService2; import com.danielkim.soundrecorder.fragments.FileViewerFragment; import com.danielkim.soundrecorder.fragments.LicensesFragment; import com.danielkim.soundrecorder.fragments.RecordFragment; import com.danielkim.soundrecorder.fragments.ScheduledRecordingsFragment; -public class MainActivity extends ActionBarActivity{ +public class MainActivity extends ActionBarActivity implements RecordingService2.OnTimerChangedListener, RecordFragment.ServiceOperationsListener { - private static final String LOG_TAG = MainActivity.class.getSimpleName(); + private static final String TAG = "SCHEDULED_RECORDER_TAG"; private PagerSlidingTabStrip tabs; private ViewPager pager; + private RecordFragment recordFragment = null; + + private RecordingService2 recordingService; + private boolean serviceConnected = false; + //@RequiresApi(api = Build.VERSION_CODES.N) @Override @@ -83,7 +93,8 @@ public MyAdapter(FragmentManager fm) { public Fragment getItem(int position) { switch(position){ case 0:{ - return RecordFragment.newInstance(position); + recordFragment = RecordFragment.newInstance(position); + return recordFragment; } case 1:{ return FileViewerFragment.newInstance(position); @@ -108,4 +119,113 @@ public CharSequence getPageTitle(int position) { public MainActivity() { } + + // Connection with local Service. + @Override + protected void onStart() { + super.onStart(); + + Log.d(TAG, "MainActivity - call bind to Service"); + startService(RecordingService2.makeIntent(this)); + bindService(RecordingService2.makeIntent(this), serviceConnection, BIND_AUTO_CREATE); + } + + // Disconnection from local Service. + @Override + protected void onStop() { + super.onStop(); + + if (serviceConnected) { + Log.d(TAG, "MainActivity - call unbind from Service"); + unbindService(serviceConnection); + if (!isRecording()) stopService(RecordingService2.makeIntent(this)); + recordingService = null; + serviceConnected = false; + if (recordFragment != null) { + recordFragment.serviceConnection(serviceConnected); + } + } + } + + /* + Implementation of RecordFragment.ServiceOperationsListener. + RecordFragment uses this interface to communicate with this Activity in order to interact + with RecordingService (the connection with the Service is managed by this Activity). + */ + @Override + public void onStartRecord() { + if (recordingService != null && !isRecording()) { + recordingService.startRecording(0); + } + } + + @Override + public void onStopRecord() { + if (recordingService != null) { + recordingService.stopRecording(); + } + } + + @Override + public boolean isConnected() { + return serviceConnected; + } + + @Override + public boolean serviceIsRecording() { + return isRecording(); + } + + /* + Implementation of ServiceConnection interface. + The interaction with the Service is managed by this Activity. + */ + private ServiceConnection serviceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName componentName, IBinder iBinder) { + Log.d(TAG, "MainActivity - Service connected"); + recordingService = ((RecordingService2.LocalBinder) iBinder).getService(); + serviceConnected = true; + if (recordFragment != null) { + recordFragment.serviceConnection(serviceConnected); + } + recordingService.setOnTimerChangedListener(MainActivity.this); + } + + @Override + public void onServiceDisconnected(ComponentName componentName) { + Log.d(TAG, "MainActivity - Service disconnected"); + recordingService = null; + serviceConnected = false; + if (recordFragment != null) { + recordFragment.serviceConnection(serviceConnected); + } + recordingService.setOnTimerChangedListener(null); + } + }; + + // Is the connected Service currently recording? + public boolean isRecording() { + if (recordingService != null) { + return recordingService.isRecording(); + } + return false; + } + + /* + Implementation of RecordingService2.OnTimerChangedListener interface. + The Service uses this interface to communicate the progress of the recording in seconds. + The caller of this method is on a separate thread. + */ + @Override + public void onTimerChanged(int seconds) { + if (recordFragment != null) { + runOnUiThread(new Runnable() { + @Override + public void run() { + recordFragment.timerChanged(seconds); + } + }); + } + } } diff --git a/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java b/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java index bf8c76c4..35a3f22d 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java +++ b/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java @@ -1,12 +1,11 @@ package com.danielkim.soundrecorder.fragments; import android.Manifest; -import android.content.Intent; +import android.content.Context; import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; import android.os.Environment; -import android.os.SystemClock; import android.support.v4.app.ActivityCompat; import android.support.v4.app.Fragment; import android.support.v4.content.ContextCompat; @@ -15,15 +14,16 @@ import android.view.ViewGroup; import android.view.WindowManager; import android.widget.Button; -import android.widget.Chronometer; import android.widget.TextView; import android.widget.Toast; import com.danielkim.soundrecorder.R; -import com.danielkim.soundrecorder.RecordingService; import com.melnykov.fab.FloatingActionButton; import java.io.File; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; /** * A simple {@link Fragment} subclass. @@ -34,28 +34,52 @@ */ public class RecordFragment extends Fragment { // Constants. + private static final String TAG = "SCHEDULED_RECORDER_TAG"; private static final int REQUEST_DANGEROUS_PERMISSION = 0; private boolean marshmallow = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M; // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER private static final String ARG_POSITION = "position"; - private static final String LOG_TAG = RecordFragment.class.getSimpleName(); - private int position; //Recording controls private FloatingActionButton mRecordButton = null; private Button mPauseButton = null; - private TextView mRecordingPrompt; - private int mRecordPromptCount = 0; - private boolean mStartRecording = true; - private boolean mPauseRecording = true; + private boolean isRecording = false; + private boolean mPauseRecording; + + private static final SimpleDateFormat mTimerFormat = new SimpleDateFormat("mm:ss", Locale.getDefault()); + private TextView tvChronometer; + + private ServiceOperationsListener serviceOperationsListener; + + + /* + Interface used to communicate with the Activity with regard to the connected Service. + */ + public interface ServiceOperationsListener { + void onStartRecord(); + + void onStopRecord(); + + boolean isConnected(); - private Chronometer mChronometer = null; - long timeWhenPaused = 0; //stores time when user clicks pause button + boolean serviceIsRecording(); + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + + try { + serviceOperationsListener = (ServiceOperationsListener) context; + } catch (ClassCastException e) { + throw new ClassCastException(context.toString() + " must implement ServiceOperationsListener"); + } + } /** * Use this factory method to create a new instance of @@ -86,18 +110,20 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View recordView = inflater.inflate(R.layout.fragment_record, container, false); - mChronometer = (Chronometer) recordView.findViewById(R.id.chronometer); + tvChronometer = (TextView) recordView.findViewById(R.id.tvChronometer); + tvChronometer.setText("00:00"); //update recording prompt text mRecordingPrompt = (TextView) recordView.findViewById(R.id.recording_status_text); mRecordButton = (FloatingActionButton) recordView.findViewById(R.id.btnRecord); mRecordButton.setColorNormal(getResources().getColor(R.color.primary)); mRecordButton.setColorPressed(getResources().getColor(R.color.primary_dark)); + mRecordButton.setEnabled(serviceOperationsListener.isConnected()); mRecordButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - if (!marshmallow || !mStartRecording) { - record(mStartRecording); + if (!marshmallow) { + startStopRecording(); } else { checkPermissions(); } @@ -114,12 +140,10 @@ public void onClick(View v) { } }); - return recordView; - } + // Are we already recording? Check necessary if Service is connected to the Activity before the Fragment is created. + checkRecording(); - private void record(boolean startRecording) { - onRecord(startRecording); - mStartRecording = !startRecording; + return recordView; } // Check dangerous permissions for Android Marshmallow+. @@ -135,7 +159,7 @@ private void checkPermissions() { } else if (writePerm && !audioPerm) { arrPermissions = new String[]{Manifest.permission.RECORD_AUDIO}; } else { - record(true); + startStopRecording(); return; } @@ -145,79 +169,77 @@ private void checkPermissions() { // Recording Start/Stop //TODO: recording pause - private void onRecord(boolean start){ - - Intent intent = new Intent(getActivity(), RecordingService.class); - - if (start) { + private void startStopRecording() { + if (!isRecording) { // start recording - mRecordButton.setImageResource(R.drawable.ic_media_stop); - //mPauseButton.setVisibility(View.VISIBLE); - Toast.makeText(getActivity(),R.string.toast_recording_start,Toast.LENGTH_SHORT).show(); File folder = new File(Environment.getExternalStorageDirectory() + "/SoundRecorder"); if (!folder.exists()) { //folder /SoundRecorder doesn't exist, create the folder folder.mkdir(); } - //start Chronometer - mChronometer.setBase(SystemClock.elapsedRealtime()); - mChronometer.start(); - mChronometer.setOnChronometerTickListener(new Chronometer.OnChronometerTickListener() { - @Override - public void onChronometerTick(Chronometer chronometer) { - if (mRecordPromptCount == 0) { - mRecordingPrompt.setText(getString(R.string.record_in_progress) + "."); - } else if (mRecordPromptCount == 1) { - mRecordingPrompt.setText(getString(R.string.record_in_progress) + ".."); - } else if (mRecordPromptCount == 2) { - mRecordingPrompt.setText(getString(R.string.record_in_progress) + "..."); - mRecordPromptCount = -1; - } - - mRecordPromptCount++; - } - }); - - //start RecordingService - getActivity().startService(intent); - //keep screen on while recording - getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + mRecordButton.setImageResource(R.drawable.ic_media_stop); + mRecordingPrompt.setText(getString(R.string.record_in_progress) + "..."); + Toast.makeText(getActivity(), R.string.toast_recording_start, Toast.LENGTH_SHORT).show(); - mRecordingPrompt.setText(getString(R.string.record_in_progress) + "."); - mRecordPromptCount++; + // Start RecordingService: send request to main Activity. + if (serviceOperationsListener != null) { + serviceOperationsListener.onStartRecord(); + } + getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); //keep screen on while recording + isRecording = true; } else { //stop recording mRecordButton.setImageResource(R.drawable.ic_mic_white_36dp); - //mPauseButton.setVisibility(View.GONE); - mChronometer.stop(); - mChronometer.setBase(SystemClock.elapsedRealtime()); - timeWhenPaused = 0; + tvChronometer.setText("00:00"); mRecordingPrompt.setText(getString(R.string.record_prompt)); - getActivity().stopService(intent); - //allow the screen to turn off again once recording is finished - getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + // Stop RecordingService: send request to main Activity. + if (serviceOperationsListener != null) { + serviceOperationsListener.onStopRecord(); + } + + getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); //allow the screen to turn off again once recording is finished + isRecording = false; } } //TODO: implement pause recording private void onPauseRecord(boolean pause) { - if (pause) { - //pause recording - mPauseButton.setCompoundDrawablesWithIntrinsicBounds - (R.drawable.ic_media_play ,0 ,0 ,0); - mRecordingPrompt.setText((String)getString(R.string.resume_recording_button).toUpperCase()); - timeWhenPaused = mChronometer.getBase() - SystemClock.elapsedRealtime(); - mChronometer.stop(); - } else { - //resume recording - mPauseButton.setCompoundDrawablesWithIntrinsicBounds - (R.drawable.ic_media_pause ,0 ,0 ,0); - mRecordingPrompt.setText((String)getString(R.string.pause_recording_button).toUpperCase()); - mChronometer.setBase(SystemClock.elapsedRealtime() + timeWhenPaused); - mChronometer.start(); + + } + + public void serviceConnection(boolean isConnected) { + mRecordButton.setEnabled(isConnected); + + // Are we already recording? + checkRecording(); + } + + private void checkRecording() { + if (serviceOperationsListener == null) return; + + if (serviceOperationsListener.isConnected() && serviceOperationsListener.serviceIsRecording()) { + mRecordButton.setImageResource(R.drawable.ic_media_stop); + mRecordingPrompt.setText(getString(R.string.record_in_progress) + "..."); + isRecording = true; } } + + public void timerChanged(int seconds) { + tvChronometer.setText(mTimerFormat.format(new Date(seconds * 1000L))); + } + + @Override + public void onDestroy() { + super.onDestroy(); + } + + @Override + public void onDetach() { + super.onDetach(); + + if (serviceOperationsListener != null) serviceOperationsListener = null; + } } \ No newline at end of file diff --git a/app/src/main/res/drawable-nodpi/example_picture.png b/app/src/main/res/drawable-nodpi/example_picture.png new file mode 100644 index 0000000000000000000000000000000000000000..e0627f532897da9f078e79c32ae6d7d5792803a4 GIT binary patch literal 1885 zcmb`I`#;l*AICqM`~7@z4sy%a;kfMVU{Y#wjfKcH5lzN&uc%zNlUoQe!=@SfB62Ch zhIIH+WyWHn_008oItI(dtX z`5WnDqUj#I`&cB6MCZ$iKJgKWcM-RcfPHv;7*Y=&i-<&eBN5@rH>t?803dk;ce3}r zH@8OgKn44$zQr2;X<&)PS3(;|aA+%GsxHjSK3o4&*dvEpxq{2Jdj3Df5entpzPu?U z?_96d`!z8@nvkt_6R~CL%qYDw|LEj_D;;WMiN_lZxbvtV$MJ%%uLPVbd|BKf+5z9s z=@(Ay5mRmBWMk9z`Fw>b_Ou~cR22}_#)zaRLR!Q>9M_^0J5cum)=J(XiY3mCo293n z7Vh~F(~yZKMD8`hj3y$-8y~Ab5E?n) zbZX1iWOaGPTRok7Lz}wBN-C(qk%x|u4MfLYH*T$iha;`r1yaLh&M%0`kOIcqTbNb%N1I)#+QR~@ zT=@qrV$f|^EVtrrPt!!$UuZ`riA(~>r)Q*&(U-y^78EVwl~J_T`3mQ_8eXTIa$kC4HrQ{ zLuf?BDM`}0tO{`9s}?(aJT;k(-&eXK zmLCa&eQt(NyO}JWB+J$Pwlry(OplRFgmx7)z#vV zSJX|k0$}cBlVIl66jTDX?+VQ{dNV^0h<#VMzt_8vQ#VuvL8_a8UN*6e%hFof$w!Bk z6jqPZPV0@3JhD(67b;lc<%xMLUQ>#*2wDw_iI#e9dQ%BqJ@(zP!E6}fqEWsu>27PF zKb+y)TK3~Zrs;TPv{l7IP=Q1zsKoF;Y&F+7GsRtuA*}=2bD8XO+pglntcz}uZqm{E zi_W7c=Wh((qT4oSga@~c`R|mn$HUrL^r_=mYotxX?jKP(<8uok z@6@h#*R@r$`aixgJH-f&im%&ujd?0 zxXvOs-Lg8rwnoh6bDEi31jrwPu|-S@XSWwTAOu0h>EY-XcCm+e-!UiJbxU@jv+jsl zXjytAvmBLOVxM7C0@IIBoLSn@SawqJSb-kj_AlofohAN4qi=T0K48)Y942G8N`hvPeNxfSodo-oT3 zjGD(FTmIEnL~oqT;H(s>oIfWvXapNO0m(19pq*W8 zyPcBi(heBm%$Auhg=b>30ZDdVXD}G7&idP)-Io=sOL=Cq*1tBFnZ79?N`6D91@VG% z*tt(W)+azvv2LTULE6g{_hsv6KF!+xptg1%rsh0gQ8TK|l#D~|1(JS)hWbm! z;z9DyVGAHvE2&ZLlN5WtxLsU5Zu>LIVO(n}oZq_L(gnT*8#p;ga8aa-SJQar_()Iu zi4BIs)**tR^Pb!H`a1ssY^ z-rYG^_nHe-I9Bt+5thSNJMhnYH+6JO9tZ@vZ}d&eKM~yr;LaDF J+8nOk|38FXU#|cF literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/fragment_record.xml b/app/src/main/res/layout/fragment_record.xml index 69cbcf73..af5ce780 100644 --- a/app/src/main/res/layout/fragment_record.xml +++ b/app/src/main/res/layout/fragment_record.xml @@ -16,15 +16,17 @@ android:layout_centerHorizontal="true" android:src="@drawable/ic_mic_white_36dp" /> - + android:layout_marginBottom="64dp" + android:fontFamily="sans-serif-light" + android:text="00:00" + android:textColor="@color/primary_text" + android:textSize="60sp" /> Registrazione... - SoundRecorder sta registrando + SoundRecorder sta registrando... Conferma cancellazione diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e90b2dc0..2d33de99 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -26,7 +26,7 @@ Recording... - SoundRecorder is currently recording + SoundRecorder is currently recording... Confirm Delete... @@ -62,4 +62,19 @@ no scheduled recordings Schedule recording + New message: %1$s + + + You said %1$s and lorem ipsum + dolor sit amet, consectetur adipiscing elit. Etiam non enim magna. Morbi dictum, velit vel + semper venenatis, magna odio volutpat velit, at ullamcorper nulla lacus sed turpis. + Pellentesque vitae metus elit, nec tincidunt tellus. Integer sed nisl sem, ullamcorper + ornare lacus. Duis ac mauris sed massa congue volutpat. Donec sed erat sit amet turpis + viverra rhoncus sit amet nec magna. Donec lacinia ligula at libero volutpat volutpat nec nec + tortor. + + + Share + Reply + From d0d1d3ba9a9e54b09e1e101ad84051b394df0c69 Mon Sep 17 00:00:00 2001 From: iClaude Date: Fri, 29 Sep 2017 20:52:06 +0200 Subject: [PATCH 50/96] working on the Service for a scheduled recording --- .../soundrecorder/RecordingService2.java | 87 +++++++++---------- .../activities/MainActivity.java | 63 +++++++++----- .../fragments/RecordFragment.java | 67 ++++++++------ 3 files changed, 125 insertions(+), 92 deletions(-) diff --git a/app/src/main/java/com/danielkim/soundrecorder/RecordingService2.java b/app/src/main/java/com/danielkim/soundrecorder/RecordingService2.java index 5ff48041..c4330ff1 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/RecordingService2.java +++ b/app/src/main/java/com/danielkim/soundrecorder/RecordingService2.java @@ -50,12 +50,9 @@ public class RecordingService2 extends Service { private MediaRecorder mRecorder = null; private DBHelper mDatabase; private long mStartingTimeMillis = 0; - private long mElapsedMillis = 0; private int mElapsedSeconds = 0; - //private ScheduledRecordingListener scheduledRecordingListener = null; private static final SimpleDateFormat mTimerFormat = new SimpleDateFormat("mm:ss", Locale.getDefault()); - private Timer mTimer = null; private TimerTask mIncrementTimerTask = null; private final IBinder myBinder = new LocalBinder(); @@ -68,8 +65,7 @@ public class RecordingService2 extends Service { recording. */ public static Intent makeIntent(Context context) { - Intent intent = new Intent(context, RecordingService2.class); - return intent; + return new Intent(context, RecordingService2.class); } /* @@ -110,18 +106,20 @@ public void setOnTimerChangedListener(OnTimerChangedListener onTimerChangedListe this.onTimerChangedListener = onTimerChangedListener; } -/* *//* - Interface used to communicate the start/stop of a scheduled recording to the connected Activity. - *//* - public interface ScheduledRecordingListener { + /* + Interface used to communicate the start/stop of a scheduled recording to a connected + Activity, so that the UI can be updated accordingly. + */ + public interface OnScheduledRecordingListener { void onScheduledRecordingStart(); void onScheduledRecordingStop(); } - public void setScheduledRecordingListener(ScheduledRecordingListener listener) { - this.scheduledRecordingListener = listener; - }*/ + private OnScheduledRecordingListener onScheduledRecordingListener = null; + public void setOnScheduledRecordingListener(OnScheduledRecordingListener listener) { + this.onScheduledRecordingListener = listener; + } /* The following code implements a started Service. @@ -137,24 +135,16 @@ public int onStartCommand(Intent intent, int flags, int startId) { duration = (int) (scheduledRecordingItem.getEnd() - scheduledRecordingItem.getStart()); // Schedule next recording. startService(ScheduledRecordingService.makeIntent(this, false)); + + if (onScheduledRecordingListener != null) { // if an Activity is connected, inform it that a scheduled recording has started + onScheduledRecordingListener.onScheduledRecordingStart(); + } startRecording(duration); } return START_NOT_STICKY; } - // Specific to scheduled recordings. - private void stopScheduledRecording() { - // Remove scheduled recording from database. - mDatabase.removeScheduledRecording(scheduledRecordingItem.getId()); - - // Save recording file to database. - stopRecording(); - stopSelf(); - /* if(scheduledRecordingListener != null) - scheduledRecordingListener.onScheduledRecordingStop();*/ - } - /* The following code is shared by both started and bound Service. */ @@ -206,16 +196,12 @@ public void onInfo(MediaRecorder mediaRecorder, int what, int extra) { isRecording = true; startTimer(); -/* if(duration > 0) { - if(scheduledRecordingListener != null) - scheduledRecordingListener.onScheduledRecordingStart(); - }*/ } catch (IOException e) { Log.e(TAG, "prepare() failed"); } } - public void setFileNameAndPath() { + private void setFileNameAndPath() { int count = 0; File f; @@ -231,15 +217,30 @@ public void setFileNameAndPath() { } while (f.exists() && !f.isDirectory()); } + private void startTimer() { + Timer mTimer = new Timer(); + mElapsedSeconds = 0; + mIncrementTimerTask = new TimerTask() { + @Override + public void run() { + mElapsedSeconds++; + if (onTimerChangedListener != null) { + onTimerChangedListener.onTimerChanged(mElapsedSeconds); + } + } + }; + mTimer.scheduleAtFixedRate(mIncrementTimerTask, 1000, 1000); + } + public void stopRecording() { Log.d(TAG, "Service - stopRecording"); mRecorder.stop(); - mElapsedMillis = (System.currentTimeMillis() - mStartingTimeMillis); + long mElapsedMillis = (System.currentTimeMillis() - mStartingTimeMillis); mRecorder.release(); isRecording = false; Toast.makeText(this, getString(R.string.toast_recording_finish) + " " + mFilePath, Toast.LENGTH_LONG).show(); - //remove notification + // Stop timer. if (mIncrementTimerTask != null) { mIncrementTimerTask.cancel(); mIncrementTimerTask = null; @@ -255,19 +256,17 @@ public void stopRecording() { stopForeground(true); } - private void startTimer() { - mTimer = new Timer(); - mElapsedSeconds = 0; - mIncrementTimerTask = new TimerTask() { - @Override - public void run() { - mElapsedSeconds++; - if (onTimerChangedListener != null) { - onTimerChangedListener.onTimerChanged(mElapsedSeconds); - } - } - }; - mTimer.scheduleAtFixedRate(mIncrementTimerTask, 1000, 1000); + // Specific to scheduled recordings. + private void stopScheduledRecording() { + // Remove scheduled recording from database. + mDatabase.removeScheduledRecording(scheduledRecordingItem.getId()); + + // Save recording file to database. + stopRecording(); + if (onScheduledRecordingListener != null) // if an Activity is connected, inform it that the scheduled recording has stopped + onScheduledRecordingListener.onScheduledRecordingStop(); + else + stopSelf(); // stop the Service if no Activity is connected } private Notification createNotification() { diff --git a/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java b/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java index f7c7e84d..295b8ffe 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java +++ b/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java @@ -23,34 +23,29 @@ import com.danielkim.soundrecorder.fragments.ScheduledRecordingsFragment; -public class MainActivity extends ActionBarActivity implements RecordingService2.OnTimerChangedListener, RecordFragment.ServiceOperationsListener { +public class MainActivity extends ActionBarActivity implements RecordingService2.OnTimerChangedListener, RecordFragment.ServiceOperations { private static final String TAG = "SCHEDULED_RECORDER_TAG"; - private PagerSlidingTabStrip tabs; - private ViewPager pager; private RecordFragment recordFragment = null; private RecordingService2 recordingService; private boolean serviceConnected = false; - //@RequiresApi(api = Build.VERSION_CODES.N) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - pager = (ViewPager) findViewById(R.id.pager); + ViewPager pager = (ViewPager) findViewById(R.id.pager); pager.setAdapter(new MyAdapter(getSupportFragmentManager())); - tabs = (PagerSlidingTabStrip) findViewById(R.id.tabs); + PagerSlidingTabStrip tabs = (PagerSlidingTabStrip) findViewById(R.id.tabs); tabs.setViewPager(pager); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); toolbar.setPopupTheme(R.style.ThemeOverlay_AppCompat_Light); - if (toolbar != null) { - setSupportActionBar(toolbar); - } + setSupportActionBar(toolbar); } @Override @@ -75,13 +70,13 @@ public boolean onOptionsItemSelected(MenuItem item) { } } - public void openLicenses(){ + private void openLicenses() { LicensesFragment licensesFragment = new LicensesFragment(); licensesFragment.show(getSupportFragmentManager().beginTransaction(), "dialog_licenses"); } public class MyAdapter extends FragmentPagerAdapter { - private String[] titles = { getString(R.string.tab_title_record), + private final String[] titles = {getString(R.string.tab_title_record), getString(R.string.tab_title_saved_recordings), getString(R.string.tab_title_scheduled_recordings)}; @@ -142,37 +137,37 @@ protected void onStop() { recordingService = null; serviceConnected = false; if (recordFragment != null) { - recordFragment.serviceConnection(serviceConnected); + recordFragment.serviceConnection(false); } } } /* - Implementation of RecordFragment.ServiceOperationsListener. + Implementation of RecordFragment.ServiceOperations. RecordFragment uses this interface to communicate with this Activity in order to interact with RecordingService (the connection with the Service is managed by this Activity). */ @Override - public void onStartRecord() { + public void requestStartRecording() { if (recordingService != null && !isRecording()) { recordingService.startRecording(0); } } @Override - public void onStopRecord() { + public void requestStopRecording() { if (recordingService != null) { recordingService.stopRecording(); } } @Override - public boolean isConnected() { + public boolean isServiceConnected() { return serviceConnected; } @Override - public boolean serviceIsRecording() { + public boolean isServiceRecording() { return isRecording(); } @@ -180,32 +175,35 @@ public boolean serviceIsRecording() { Implementation of ServiceConnection interface. The interaction with the Service is managed by this Activity. */ - private ServiceConnection serviceConnection = new ServiceConnection() { + private final ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { Log.d(TAG, "MainActivity - Service connected"); recordingService = ((RecordingService2.LocalBinder) iBinder).getService(); serviceConnected = true; if (recordFragment != null) { - recordFragment.serviceConnection(serviceConnected); + recordFragment.serviceConnection(true); } recordingService.setOnTimerChangedListener(MainActivity.this); + recordingService.setOnScheduledRecordingListener(onScheduledRecordingListener); } @Override public void onServiceDisconnected(ComponentName componentName) { Log.d(TAG, "MainActivity - Service disconnected"); + recordingService.setOnTimerChangedListener(null); + recordingService.setOnScheduledRecordingListener(null); recordingService = null; serviceConnected = false; if (recordFragment != null) { - recordFragment.serviceConnection(serviceConnected); + recordFragment.serviceConnection(false); } - recordingService.setOnTimerChangedListener(null); } }; // Is the connected Service currently recording? - public boolean isRecording() { + private boolean isRecording() { + //noinspection SimplifiableIfStatement if (recordingService != null) { return recordingService.isRecording(); } @@ -215,7 +213,7 @@ public boolean isRecording() { /* Implementation of RecordingService2.OnTimerChangedListener interface. The Service uses this interface to communicate the progress of the recording in seconds. - The caller of this method is on a separate thread. + The caller of this method runs on a separate thread. */ @Override public void onTimerChanged(int seconds) { @@ -228,4 +226,23 @@ public void run() { }); } } + + /* + Implementation of RecordingService2.OnScheduledRecordingListener interface. + The Service uses this interface to communicate to the connected Activity that a + scheduled recording has started/stopped, so that the UI can be updated accordingly. + */ + private final RecordingService2.OnScheduledRecordingListener onScheduledRecordingListener = new RecordingService2.OnScheduledRecordingListener() { + @Override + public void onScheduledRecordingStart() { + if (recordFragment != null) + recordFragment.scheduledRecordingStarted(); + } + + @Override + public void onScheduledRecordingStop() { + if (recordFragment != null) + recordFragment.scheduledRecordingStopped(); + } + }; } diff --git a/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java b/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java index 35a3f22d..d3a6c416 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java +++ b/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java @@ -37,15 +37,13 @@ public class RecordFragment extends Fragment { private static final String TAG = "SCHEDULED_RECORDER_TAG"; private static final int REQUEST_DANGEROUS_PERMISSION = 0; - private boolean marshmallow = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M; + private final boolean marshmallow = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M; // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER private static final String ARG_POSITION = "position"; - private int position; //Recording controls private FloatingActionButton mRecordButton = null; - private Button mPauseButton = null; private TextView mRecordingPrompt; private boolean isRecording = false; @@ -54,20 +52,20 @@ public class RecordFragment extends Fragment { private static final SimpleDateFormat mTimerFormat = new SimpleDateFormat("mm:ss", Locale.getDefault()); private TextView tvChronometer; - private ServiceOperationsListener serviceOperationsListener; + private ServiceOperations serviceOperations; /* Interface used to communicate with the Activity with regard to the connected Service. */ - public interface ServiceOperationsListener { - void onStartRecord(); + public interface ServiceOperations { + void requestStartRecording(); - void onStopRecord(); + void requestStopRecording(); - boolean isConnected(); + boolean isServiceConnected(); - boolean serviceIsRecording(); + boolean isServiceRecording(); } @Override @@ -75,9 +73,9 @@ public void onAttach(Context context) { super.onAttach(context); try { - serviceOperationsListener = (ServiceOperationsListener) context; + serviceOperations = (ServiceOperations) context; } catch (ClassCastException e) { - throw new ClassCastException(context.toString() + " must implement ServiceOperationsListener"); + throw new ClassCastException(context.toString() + " must implement ServiceOperations"); } } @@ -102,7 +100,7 @@ public RecordFragment() { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - position = getArguments().getInt(ARG_POSITION); + int position = getArguments().getInt(ARG_POSITION); } @Override @@ -118,7 +116,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, mRecordButton = (FloatingActionButton) recordView.findViewById(R.id.btnRecord); mRecordButton.setColorNormal(getResources().getColor(R.color.primary)); mRecordButton.setColorPressed(getResources().getColor(R.color.primary_dark)); - mRecordButton.setEnabled(serviceOperationsListener.isConnected()); + mRecordButton.setEnabled(serviceOperations.isServiceConnected()); mRecordButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -130,7 +128,7 @@ public void onClick(View v) { } }); - mPauseButton = (Button) recordView.findViewById(R.id.btnPause); + Button mPauseButton = (Button) recordView.findViewById(R.id.btnPause); mPauseButton.setVisibility(View.GONE); //hide pause button before recording starts mPauseButton.setOnClickListener(new View.OnClickListener() { @Override @@ -140,7 +138,10 @@ public void onClick(View v) { } }); - // Are we already recording? Check necessary if Service is connected to the Activity before the Fragment is created. + /* Are we already recording? Check necessary if Service is connected to the Activity + before the Fragment is created: in this case the method serviceConnection(boolean + isConnected of this Fragment is not called). + */ checkRecording(); return recordView; @@ -183,8 +184,8 @@ private void startStopRecording() { Toast.makeText(getActivity(), R.string.toast_recording_start, Toast.LENGTH_SHORT).show(); // Start RecordingService: send request to main Activity. - if (serviceOperationsListener != null) { - serviceOperationsListener.onStartRecord(); + if (serviceOperations != null) { + serviceOperations.requestStartRecording(); } getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); //keep screen on while recording @@ -196,8 +197,8 @@ private void startStopRecording() { mRecordingPrompt.setText(getString(R.string.record_prompt)); // Stop RecordingService: send request to main Activity. - if (serviceOperationsListener != null) { - serviceOperationsListener.onStopRecord(); + if (serviceOperations != null) { + serviceOperations.requestStopRecording(); } getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); //allow the screen to turn off again once recording is finished @@ -210,6 +211,10 @@ private void onPauseRecord(boolean pause) { } + /* + When the Activity establishes the connection with the Service, it informs this Fragment + so that the record button can be enabled. + */ public void serviceConnection(boolean isConnected) { mRecordButton.setEnabled(isConnected); @@ -217,10 +222,14 @@ public void serviceConnection(boolean isConnected) { checkRecording(); } + /* + If the Service is currently recording update the UI accordingly and update the value + of isRecording. + */ private void checkRecording() { - if (serviceOperationsListener == null) return; + if (serviceOperations == null) return; - if (serviceOperationsListener.isConnected() && serviceOperationsListener.serviceIsRecording()) { + if (serviceOperations.isServiceConnected() && serviceOperations.isServiceRecording()) { mRecordButton.setImageResource(R.drawable.ic_media_stop); mRecordingPrompt.setText(getString(R.string.record_in_progress) + "..."); isRecording = true; @@ -231,15 +240,23 @@ public void timerChanged(int seconds) { tvChronometer.setText(mTimerFormat.format(new Date(seconds * 1000L))); } - @Override - public void onDestroy() { - super.onDestroy(); + public void scheduledRecordingStarted() { + mRecordButton.setImageResource(R.drawable.ic_media_stop); + mRecordingPrompt.setText(getString(R.string.record_in_progress) + "..."); + isRecording = true; + } + + public void scheduledRecordingStopped() { + mRecordButton.setImageResource(R.drawable.ic_mic_white_36dp); + tvChronometer.setText("00:00"); + mRecordingPrompt.setText(getString(R.string.record_prompt)); + isRecording = false; } @Override public void onDetach() { super.onDetach(); - if (serviceOperationsListener != null) serviceOperationsListener = null; + if (serviceOperations != null) serviceOperations = null; } } \ No newline at end of file From 233f6390cb024263e0de876eedcb0329b748b588 Mon Sep 17 00:00:00 2001 From: iClaude Date: Mon, 2 Oct 2017 15:31:15 +0200 Subject: [PATCH 51/96] hybrid Service implemented --- app/src/main/AndroidManifest.xml | 1 - .../soundrecorder/RecordingService.java | 194 ++++++++---- .../soundrecorder/RecordingService2.java | 289 ------------------ .../activities/MainActivity.java | 20 +- .../fragments/RecordFragment.java | 25 +- 5 files changed, 156 insertions(+), 373 deletions(-) delete mode 100644 app/src/main/java/com/danielkim/soundrecorder/RecordingService2.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 68cd0c91..47f9a783 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -40,7 +40,6 @@ - Date: Mon, 2 Oct 2017 20:49:54 +0200 Subject: [PATCH 52/96] NDC - working on scheduled recordings - still bugs to fix --- .../soundrecorder/RecordingService.java | 17 ++++++++--------- .../ScheduledRecordingService.java | 3 ++- .../AddScheduledRecordingActivity.java | 1 + .../soundrecorder/fragments/RecordFragment.java | 3 +++ 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java b/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java index 09c5f095..b9769261 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java +++ b/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java @@ -23,8 +23,6 @@ import java.io.File; import java.io.IOException; -import java.text.SimpleDateFormat; -import java.util.Locale; import java.util.Timer; import java.util.TimerTask; @@ -51,11 +49,9 @@ public class RecordingService extends Service { private long mStartingTimeMillis = 0; private int mElapsedSeconds = 0; - private static final SimpleDateFormat mTimerFormat = new SimpleDateFormat("mm:ss", Locale.getDefault()); private TimerTask mIncrementTimerTask = null; private final IBinder myBinder = new LocalBinder(); - private ScheduledRecordingItem scheduledRecordingItem = null; private boolean isRecording = false; @@ -74,6 +70,7 @@ public static Intent makeIntent(Context context) { public static Intent makeIntent(Context context, ScheduledRecordingItem item) { Intent intent = new Intent(context, RecordingService.class); intent.putExtra(EXTRA_ITEM, item); + Log.d(TAG, "Recording Service makeIntent - intent extras are null? " + new Boolean(item == null)); return intent; } @@ -126,10 +123,11 @@ public void setOnScheduledRecordingListener(OnScheduledRecordingListener listene @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d(TAG, "Service - onStartCommand"); - scheduledRecordingItem = intent.getParcelableExtra(EXTRA_ITEM); - - int duration = 0; + ScheduledRecordingItem scheduledRecordingItem = intent.getParcelableExtra(EXTRA_ITEM); + Log.d(TAG, "Recording Service onStartCommand - intent extras are null? " + new Boolean(scheduledRecordingItem == null)); + int duration; if (scheduledRecordingItem != null) { // automatic scheduled recording + Log.d(TAG, "Service - onStartCommand - scheduled recording"); duration = (int) (scheduledRecordingItem.getEnd() - scheduledRecordingItem.getStart()); // Remove scheduled recording from database and schedule next recording. mDatabase.removeScheduledRecording(scheduledRecordingItem.getId()); @@ -142,7 +140,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { startRecording(duration); } - return START_NOT_STICKY; + return START_REDELIVER_INTENT; } /* @@ -258,7 +256,8 @@ public void stopRecording() { // Specific to scheduled recordings. private void stopScheduledRecording() { - // Stop recording as usuale. + Log.d(TAG, "Service - stopScheduledRecording"); + // Stop recording as usual. stopRecording(); /* If an Activity is connected, inform it that the scheduled recording has stopped, diff --git a/app/src/main/java/com/danielkim/soundrecorder/ScheduledRecordingService.java b/app/src/main/java/com/danielkim/soundrecorder/ScheduledRecordingService.java index e030d117..12e55273 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/ScheduledRecordingService.java +++ b/app/src/main/java/com/danielkim/soundrecorder/ScheduledRecordingService.java @@ -24,6 +24,7 @@ public class ScheduledRecordingService extends Service implements Handler.Callback { private final int SCHEDULE_RECORDINGS = 1; + private static final String TAG = "SCHEDULED_RECORDER_TAG"; protected static final String EXTRA_WAKEFUL = "com.danielkim.soundrecorder.WAKEFUL"; protected AlarmManager alarmManager; @@ -116,7 +117,7 @@ protected void scheduleNextRecording() { ScheduledRecordingItem item = database.getNextScheduledRecording(); if (item != null) { Intent intent = RecordingService.makeIntent(context, item); - PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 0); + PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR2) { // up to API 18 alarmManager.set(AlarmManager.RTC_WAKEUP, item.getStart(), pendingIntent); } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2 && Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) { // API 19-22 diff --git a/app/src/main/java/com/danielkim/soundrecorder/activities/AddScheduledRecordingActivity.java b/app/src/main/java/com/danielkim/soundrecorder/activities/AddScheduledRecordingActivity.java index 003a07bc..125e90a2 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/activities/AddScheduledRecordingActivity.java +++ b/app/src/main/java/com/danielkim/soundrecorder/activities/AddScheduledRecordingActivity.java @@ -55,6 +55,7 @@ private interface StatusCodes { int ERROR_SAVING = 4; } + private static final String TAG = "SCHEDULED_RECORDER_TAG"; private static final String EXTRA_DATE_LONG = "com.danielkim.soundrecorder.activities.EXTRA_DATE_LONG"; private static final String EXTRA_ITEM = "com.danielkim.soundrecorder.activities.EXTRA_ITEM"; diff --git a/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java b/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java index d430cf16..07e73c4f 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java +++ b/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java @@ -9,6 +9,7 @@ import android.support.v4.app.ActivityCompat; import android.support.v4.app.Fragment; import android.support.v4.content.ContextCompat; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -249,11 +250,13 @@ public void timerChanged(int seconds) { } public void scheduledRecordingStarted() { + Log.d(TAG, "RecordFragment - scheduledRecordingStarted"); updateUI(true); isRecording = true; } public void scheduledRecordingStopped() { + Log.d(TAG, "RecordFragment - scheduledRecordingStopped"); updateUI(false); isRecording = false; } From 17741bb6631105c20c68f9e21ae10cff3c294101 Mon Sep 17 00:00:00 2001 From: iClaude Date: Wed, 4 Oct 2017 16:11:28 +0200 Subject: [PATCH 53/96] NDC - onStartCommand and makeIntent refactored --- .../soundrecorder/RecordingService.java | 49 +++++++++---------- .../ScheduledRecordingService.java | 6 +-- .../activities/MainActivity.java | 4 +- 3 files changed, 29 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java b/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java index b9769261..c4a86218 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java +++ b/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java @@ -39,7 +39,7 @@ public class RecordingService extends Service { private static final String TAG = "SCHEDULED_RECORDER_TAG"; - private static final String EXTRA_ITEM = "com.danielkim.soundrecorder.ITEM"; + private static final String EXTRA_ACTIVITY_STARTER = "com.danielkim.soundrecorder.EXTRA_ACTIVITY_STARTER"; private static final int ONGOING_NOTIFICATION = 1; private String mFileName = null; @@ -56,21 +56,21 @@ public class RecordingService extends Service { /* - Static factory method used to create an Intent to start this Service for a normal - recording. + Static factory method used to create an Intent to start this Service. The boolean value + activityStarter is true if this method is called by an Activity, false otherwise (i.e. + Service started by an AlarmManager for a scheduled recording. */ - public static Intent makeIntent(Context context) { - return new Intent(context, RecordingService.class); + public static Intent makeIntent(Context context, boolean activityStarter) { + Intent intent = new Intent(context, RecordingService.class); + intent.putExtra(EXTRA_ACTIVITY_STARTER, activityStarter); + return intent; } /* - Static factory method used to create an Intent to start this Service for - a scheduled recording. - */ - public static Intent makeIntent(Context context, ScheduledRecordingItem item) { + Other convenient method used to retrieve an empty Intent (i.e to stop this Service). + */ + public static Intent makeIntent(Context context) { Intent intent = new Intent(context, RecordingService.class); - intent.putExtra(EXTRA_ITEM, item); - Log.d(TAG, "Recording Service makeIntent - intent extras are null? " + new Boolean(item == null)); return intent; } @@ -122,25 +122,24 @@ public void setOnScheduledRecordingListener(OnScheduledRecordingListener listene */ @Override public int onStartCommand(Intent intent, int flags, int startId) { - Log.d(TAG, "Service - onStartCommand"); - ScheduledRecordingItem scheduledRecordingItem = intent.getParcelableExtra(EXTRA_ITEM); - Log.d(TAG, "Recording Service onStartCommand - intent extras are null? " + new Boolean(scheduledRecordingItem == null)); + boolean activityStarter = intent.getBooleanExtra(EXTRA_ACTIVITY_STARTER, false); int duration; - if (scheduledRecordingItem != null) { // automatic scheduled recording - Log.d(TAG, "Service - onStartCommand - scheduled recording"); - duration = (int) (scheduledRecordingItem.getEnd() - scheduledRecordingItem.getStart()); + if (!activityStarter) { // automatic scheduled recording + // Get next recording data. + ScheduledRecordingItem item = mDatabase.getNextScheduledRecording(); + duration = (int) (item.getEnd() - item.getStart()); // Remove scheduled recording from database and schedule next recording. - mDatabase.removeScheduledRecording(scheduledRecordingItem.getId()); + mDatabase.removeScheduledRecording(item.getId()); startService(ScheduledRecordingService.makeIntent(this, false)); - if (onScheduledRecordingListener != null) { // if an Activity is connected, inform it that a scheduled recording has started onScheduledRecordingListener.onScheduledRecordingStart(); } + startForeground(ONGOING_NOTIFICATION, createNotification()); startRecording(duration); } - return START_REDELIVER_INTENT; + return START_NOT_STICKY; } /* @@ -149,14 +148,14 @@ public int onStartCommand(Intent intent, int flags, int startId) { @Override public void onCreate() { - Log.d(TAG, "Service - onCreate"); + Log.d(TAG, "RecordingService - onCreate"); super.onCreate(); mDatabase = new DBHelper(getApplicationContext()); } @Override public void onDestroy() { - Log.d(TAG, "Service - onDestroy"); + Log.d(TAG, "RecordingService - onDestroy"); super.onDestroy(); if (mRecorder != null) { stopRecording(); @@ -166,7 +165,7 @@ public void onDestroy() { } public void startRecording(int duration) { - Log.d(TAG, "Service - startRecording"); + Log.d(TAG, "RecordingService - startRecording"); startForeground(ONGOING_NOTIFICATION, createNotification()); setFileNameAndPath(); @@ -231,7 +230,7 @@ public void run() { } public void stopRecording() { - Log.d(TAG, "Service - stopRecording"); + Log.d(TAG, "RecordingService - stopRecording"); mRecorder.stop(); long mElapsedMillis = (System.currentTimeMillis() - mStartingTimeMillis); mRecorder.release(); @@ -256,7 +255,7 @@ public void stopRecording() { // Specific to scheduled recordings. private void stopScheduledRecording() { - Log.d(TAG, "Service - stopScheduledRecording"); + Log.d(TAG, "RecordingService - stopScheduledRecording"); // Stop recording as usual. stopRecording(); diff --git a/app/src/main/java/com/danielkim/soundrecorder/ScheduledRecordingService.java b/app/src/main/java/com/danielkim/soundrecorder/ScheduledRecordingService.java index 12e55273..ed80379f 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/ScheduledRecordingService.java +++ b/app/src/main/java/com/danielkim/soundrecorder/ScheduledRecordingService.java @@ -106,7 +106,7 @@ public boolean handleMessage(Message message) { // Cancels all pending alarms already set in the AlarmManager. protected void resetAlarmManager() { - Intent intent = RecordingService.makeIntent(context, null); + Intent intent = RecordingService.makeIntent(context); PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 0); alarmManager.cancel(pendingIntent); } @@ -116,8 +116,8 @@ protected void scheduleNextRecording() { DBHelper database = new DBHelper(context); ScheduledRecordingItem item = database.getNextScheduledRecording(); if (item != null) { - Intent intent = RecordingService.makeIntent(context, item); - PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); + Intent intent = RecordingService.makeIntent(getApplicationContext(), false); + PendingIntent pendingIntent = PendingIntent.getService(getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR2) { // up to API 18 alarmManager.set(AlarmManager.RTC_WAKEUP, item.getStart(), pendingIntent); } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2 && Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) { // API 19-22 diff --git a/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java b/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java index 3f82441c..dcaaea4a 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java +++ b/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java @@ -121,8 +121,8 @@ protected void onStart() { super.onStart(); Log.d(TAG, "MainActivity - call bind to Service"); - startService(RecordingService.makeIntent(this)); - bindService(RecordingService.makeIntent(this), serviceConnection, BIND_AUTO_CREATE); + startService(RecordingService.makeIntent(this, true)); + bindService(RecordingService.makeIntent(this, true), serviceConnection, BIND_AUTO_CREATE); } // Disconnection from local Service. From e0210974950a74df55596944ded62e2498bab346 Mon Sep 17 00:00:00 2001 From: iClaude Date: Wed, 4 Oct 2017 20:56:26 +0200 Subject: [PATCH 54/96] hybrid Service completed --- .../soundrecorder/RecordingService.java | 2 +- others/notes | 17 ++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java b/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java index c4a86218..4cfe2acf 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java +++ b/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java @@ -123,6 +123,7 @@ public void setOnScheduledRecordingListener(OnScheduledRecordingListener listene @Override public int onStartCommand(Intent intent, int flags, int startId) { boolean activityStarter = intent.getBooleanExtra(EXTRA_ACTIVITY_STARTER, false); + Log.d(TAG, "RecordingService - onStartCommand - activity starter? " + activityStarter); int duration; if (!activityStarter) { // automatic scheduled recording // Get next recording data. @@ -135,7 +136,6 @@ public int onStartCommand(Intent intent, int flags, int startId) { if (onScheduledRecordingListener != null) { // if an Activity is connected, inform it that a scheduled recording has started onScheduledRecordingListener.onScheduledRecordingStart(); } - startForeground(ONGOING_NOTIFICATION, createNotification()); startRecording(duration); } diff --git a/others/notes b/others/notes index 4165a3bd..c8b741ed 100644 --- a/others/notes +++ b/others/notes @@ -1,9 +1,8 @@ -what's next: -- create minimal interface for adding a scheduled recording -- test the app -- improve the UI - -TODO: -- check dangerous permissions when inserting a scheduled recorder -- RecordingService: what if a scheduled recording is ongoing and the user presses the record - button? \ No newline at end of file +bug da sistemare: +- quando si imposta una registrazione schedulizzata bisogna controllare se ci sono i permessi + di scrittura +- quando si lancia una registrazione normale e vengono chiesti i permessi, una volta dati bisogna + far partire la registrazione +- registrazione schedulizzata normale (senza activity connessa): funziona +- registrazione schedulizzata con activity connessa: funziona +- lancio registrazione manuale e scatta una registrazione schedulizzata: cosa succede? \ No newline at end of file From ecdb6a6934849013c1d520ea5303988162cbdd73 Mon Sep 17 00:00:00 2001 From: iClaude Date: Fri, 6 Oct 2017 13:38:33 +0200 Subject: [PATCH 55/96] bugs to fix and new things to do --- others/notes | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/others/notes b/others/notes index c8b741ed..bc72c6a8 100644 --- a/others/notes +++ b/others/notes @@ -3,6 +3,10 @@ bug da sistemare: di scrittura - quando si lancia una registrazione normale e vengono chiesti i permessi, una volta dati bisogna far partire la registrazione -- registrazione schedulizzata normale (senza activity connessa): funziona -- registrazione schedulizzata con activity connessa: funziona -- lancio registrazione manuale e scatta una registrazione schedulizzata: cosa succede? \ No newline at end of file +- lancio registrazione manuale e scatta una registrazione schedulizzata: cosa succede? +- quando termina una registrazione schedulizzata il programma si arresta (forse dipende dal toast + che non deve essere visualizzato ad app spenta) +- miglioramento: se creassi un Service come classe astratta e due estensioni concrete per + registrazioni normali e schedulizzate? +- usare dependency injection per DBHelper con Dagger 2 +- guardare il codice e cercare uso operatore new all’interno delle classi: si può sostituire con DI? From 919585ad220026cfb714ac6722e1e6512c212eca Mon Sep 17 00:00:00 2001 From: iClaude Date: Fri, 6 Oct 2017 16:12:52 +0200 Subject: [PATCH 56/96] bugs with connected Activity fixed --- .../soundrecorder/RecordingService.java | 41 +++++++++----- .../activities/MainActivity.java | 36 +++++++++---- .../fragments/RecordFragment.java | 44 ++++++++++----- .../ScheduledRecordingsFragment.java | 53 ++++++++++++++++++- app/src/main/res/values-it/strings.xml | 2 + app/src/main/res/values/strings.xml | 1 + others/notes | 17 +++--- 7 files changed, 147 insertions(+), 47 deletions(-) diff --git a/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java b/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java index 4cfe2acf..60e35143 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java +++ b/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java @@ -16,7 +16,6 @@ import android.os.IBinder; import android.support.v4.app.NotificationCompat; import android.util.Log; -import android.widget.Toast; import com.danielkim.soundrecorder.activities.MainActivity; import com.danielkim.soundrecorder.database.DBHelper; @@ -90,16 +89,20 @@ public IBinder onBind(Intent intent) { } /* - Interface used to communicate the seconds of the current recording to the connected Activity. + Interface used to communicate to a connected Activity: the seconds elapsed and the end + of a recording with file path. */ - public interface OnTimerChangedListener { + public interface OnRecordingStatusChangedListener { + void onRecordingStarted(); void onTimerChanged(int seconds); + + void onRecordingStopped(String filePath); } - private OnTimerChangedListener onTimerChangedListener = null; + private OnRecordingStatusChangedListener onRecordingStatusChangedListener = null; - public void setOnTimerChangedListener(OnTimerChangedListener onTimerChangedListener) { - this.onTimerChangedListener = onTimerChangedListener; + public void setOnRecordingStatusChangedListener(OnRecordingStatusChangedListener onRecordingStatusChangedListener) { + this.onRecordingStatusChangedListener = onRecordingStatusChangedListener; } /* @@ -133,10 +136,11 @@ public int onStartCommand(Intent intent, int flags, int startId) { mDatabase.removeScheduledRecording(item.getId()); startService(ScheduledRecordingService.makeIntent(this, false)); - if (onScheduledRecordingListener != null) { // if an Activity is connected, inform it that a scheduled recording has started - onScheduledRecordingListener.onScheduledRecordingStart(); + if (!isRecording) { + if (onScheduledRecordingListener != null) // if an Activity is connected, inform it that a scheduled recording has started + onScheduledRecordingListener.onScheduledRecordingStart(); + startRecording(duration); } - startRecording(duration); } return START_NOT_STICKY; @@ -161,7 +165,7 @@ public void onDestroy() { stopRecording(); } - if (onTimerChangedListener != null) onTimerChangedListener = null; + if (onRecordingStatusChangedListener != null) onRecordingStatusChangedListener = null; } public void startRecording(int duration) { @@ -196,6 +200,10 @@ public void onInfo(MediaRecorder mediaRecorder, int what, int extra) { } catch (IOException e) { Log.e(TAG, "prepare() failed"); } + + if (onRecordingStatusChangedListener != null) { + onRecordingStatusChangedListener.onRecordingStarted(); + } } private void setFileNameAndPath() { @@ -221,8 +229,8 @@ private void startTimer() { @Override public void run() { mElapsedSeconds++; - if (onTimerChangedListener != null) { - onTimerChangedListener.onTimerChanged(mElapsedSeconds); + if (onRecordingStatusChangedListener != null) { + onRecordingStatusChangedListener.onTimerChanged(mElapsedSeconds); } } }; @@ -235,7 +243,12 @@ public void stopRecording() { long mElapsedMillis = (System.currentTimeMillis() - mStartingTimeMillis); mRecorder.release(); isRecording = false; - Toast.makeText(this, getString(R.string.toast_recording_finish) + " " + mFilePath, Toast.LENGTH_LONG).show(); + mRecorder = null; + + // Communicate the file path to the connected Activity. + if (onRecordingStatusChangedListener != null) { + onRecordingStatusChangedListener.onRecordingStopped(mFilePath); + } // Stop timer. if (mIncrementTimerTask != null) { @@ -243,7 +256,7 @@ public void stopRecording() { mIncrementTimerTask = null; } - mRecorder = null; + // Save the recording data in the database. try { mDatabase.addRecording(mFileName, mFilePath, mElapsedMillis); } catch (Exception e) { diff --git a/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java b/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java index dcaaea4a..728a3212 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java +++ b/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java @@ -23,7 +23,7 @@ import com.danielkim.soundrecorder.fragments.ScheduledRecordingsFragment; -public class MainActivity extends ActionBarActivity implements RecordingService.OnTimerChangedListener, RecordFragment.ServiceOperations { +public class MainActivity extends ActionBarActivity implements RecordingService.OnRecordingStatusChangedListener, RecordFragment.ServiceOperations { private static final String TAG = "SCHEDULED_RECORDER_TAG"; @@ -184,14 +184,14 @@ public void onServiceConnected(ComponentName componentName, IBinder iBinder) { if (recordFragment != null) { recordFragment.serviceConnection(true); } - recordingService.setOnTimerChangedListener(MainActivity.this); + recordingService.setOnRecordingStatusChangedListener(MainActivity.this); recordingService.setOnScheduledRecordingListener(onScheduledRecordingListener); } @Override public void onServiceDisconnected(ComponentName componentName) { Log.d(TAG, "MainActivity - Service disconnected"); - recordingService.setOnTimerChangedListener(null); + recordingService.setOnRecordingStatusChangedListener(null); recordingService.setOnScheduledRecordingListener(null); recordingService = null; serviceConnected = false; @@ -211,10 +211,18 @@ private boolean isRecording() { } /* - Implementation of RecordingService.OnTimerChangedListener interface. - The Service uses this interface to communicate the progress of the recording in seconds. - The caller of this method runs on a separate thread. + Implementation of RecordingService.OnRecordingStatusChangedListener interface. + The Service uses this interface to communicate the status of the recording (started, + stopped and the seconds elapsed). */ + + @Override + public void onRecordingStarted() { + if (recordFragment != null) + recordFragment.updateUI(true, null); + } + + // This method is called from a separate thread. @Override public void onTimerChanged(int seconds) { if (recordFragment != null) { @@ -227,11 +235,18 @@ public void run() { } } + @Override + public void onRecordingStopped(String filePath) { + if (recordFragment != null) + recordFragment.updateUI(false, filePath); + + } + /* - Implementation of RecordingService.OnScheduledRecordingListener interface. - The Service uses this interface to communicate to the connected Activity that a - scheduled recording has started/stopped, so that the UI can be updated accordingly. - */ + Implementation of RecordingService.OnScheduledRecordingListener interface. + The Service uses this interface to communicate to the connected Activity that a + scheduled recording has started/stopped, so that the UI can be updated accordingly. + */ private final RecordingService.OnScheduledRecordingListener onScheduledRecordingListener = new RecordingService.OnScheduledRecordingListener() { @Override public void onScheduledRecordingStart() { @@ -245,4 +260,5 @@ public void onScheduledRecordingStop() { recordFragment.scheduledRecordingStopped(); } }; + } diff --git a/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java b/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java index 07e73c4f..f1948b9e 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java +++ b/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java @@ -6,6 +6,7 @@ import android.os.Build; import android.os.Bundle; import android.os.Environment; +import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; import android.support.v4.app.Fragment; import android.support.v4.content.ContextCompat; @@ -36,7 +37,7 @@ public class RecordFragment extends Fragment { // Constants. private static final String TAG = "SCHEDULED_RECORDER_TAG"; - private static final int REQUEST_DANGEROUS_PERMISSION = 0; + private static final int REQUEST_DANGEROUS_PERMISSIONS = 0; private final boolean marshmallow = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M; @@ -166,23 +167,38 @@ private void checkPermissions() { } // Request permissions. - ActivityCompat.requestPermissions(getActivity(), arrPermissions, REQUEST_DANGEROUS_PERMISSION); + ActivityCompat.requestPermissions(getActivity(), arrPermissions, REQUEST_DANGEROUS_PERMISSIONS); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + boolean granted = true; + for (int i = 0; i < grantResults.length; i++) { // we nee all permissions granted + if (grantResults[i] != PackageManager.PERMISSION_GRANTED) + granted = false; + } + + switch (requestCode) { + case REQUEST_DANGEROUS_PERMISSIONS: + if (granted) + startStopRecording(); + else + Toast.makeText(getActivity(), getString(R.string.toast_permissions_denied), Toast.LENGTH_LONG).show(); + + break; + } } // Recording Start/Stop //TODO: recording pause private void startStopRecording() { - if (!isRecording) { - // start recording + if (!isRecording) { // start recording File folder = new File(Environment.getExternalStorageDirectory() + "/SoundRecorder"); if (!folder.exists()) { //folder /SoundRecorder doesn't exist, create the folder folder.mkdir(); } - updateUI(true); - Toast.makeText(getActivity(), R.string.toast_recording_start, Toast.LENGTH_SHORT).show(); - // Start RecordingService: send request to main Activity. if (serviceOperations != null) { serviceOperations.requestStartRecording(); @@ -190,10 +206,7 @@ private void startStopRecording() { getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); //keep screen on while recording isRecording = true; - } else { - //stop recording - updateUI(false); - + } else { //stop recording // Stop RecordingService: send request to main Activity. if (serviceOperations != null) { serviceOperations.requestStopRecording(); @@ -204,14 +217,17 @@ private void startStopRecording() { } } - private void updateUI(boolean recording) { + public void updateUI(boolean recording, String filePath) { if (recording) { mRecordButton.setImageResource(R.drawable.ic_media_stop); mRecordingPrompt.setText(getString(R.string.record_in_progress) + "..."); + Toast.makeText(getActivity(), R.string.toast_recording_start, Toast.LENGTH_SHORT).show(); } else { mRecordButton.setImageResource(R.drawable.ic_mic_white_36dp); tvChronometer.setText("00:00"); mRecordingPrompt.setText(getString(R.string.record_prompt)); + if (filePath != null) + Toast.makeText(getActivity(), getString(R.string.toast_recording_finish) + " " + filePath, Toast.LENGTH_LONG).show(); } } @@ -251,13 +267,13 @@ public void timerChanged(int seconds) { public void scheduledRecordingStarted() { Log.d(TAG, "RecordFragment - scheduledRecordingStarted"); - updateUI(true); + updateUI(true, null); isRecording = true; } public void scheduledRecordingStopped() { Log.d(TAG, "RecordFragment - scheduledRecordingStopped"); - updateUI(false); + updateUI(false, null); isRecording = false; } diff --git a/app/src/main/java/com/danielkim/soundrecorder/fragments/ScheduledRecordingsFragment.java b/app/src/main/java/com/danielkim/soundrecorder/fragments/ScheduledRecordingsFragment.java index acc5032e..6ac689c4 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/fragments/ScheduledRecordingsFragment.java +++ b/app/src/main/java/com/danielkim/soundrecorder/fragments/ScheduledRecordingsFragment.java @@ -4,13 +4,17 @@ package com.danielkim.soundrecorder.fragments; +import android.Manifest; import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; +import android.content.pm.PackageManager; import android.os.AsyncTask; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.v4.app.ActivityCompat; import android.support.v4.app.Fragment; import android.support.v4.content.ContextCompat; import android.support.v7.widget.LinearLayoutManager; @@ -48,6 +52,7 @@ public class ScheduledRecordingsFragment extends Fragment implements ScheduledRecordingsFragmentItemAdapter.MyOnItemClickListener { private static final String ARG_POSITION = "position"; + private static final int REQUEST_DANGEROUS_PERMISSIONS = 0; private static final int ADD_SCHEDULED_RECORDING = 0; private static final int EDIT_SCHEDULED_RECORDING = 1; private static final String TAG = "SRFragment"; @@ -211,11 +216,55 @@ protected void onPostExecute(Integer result) { private final View.OnClickListener addScheduledRecordingListener = new View.OnClickListener() { @Override public void onClick(View view) { - Intent intent = AddScheduledRecordingActivity.makeIntent(getActivity(), selectedDate.getTime()); - startActivityForResult(intent, ADD_SCHEDULED_RECORDING); + checkPermissions(); } }; + // Check dangerous permissions for Android Marshmallow+. + private void checkPermissions() { + // Check permissions. + boolean writePerm = ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; + boolean audioPerm = ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED; + String[] arrPermissions; + if (!writePerm && !audioPerm) { + arrPermissions = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO}; + } else if (!writePerm && audioPerm) { + arrPermissions = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; + } else if (writePerm && !audioPerm) { + arrPermissions = new String[]{Manifest.permission.RECORD_AUDIO}; + } else { + startAddScheduledRecordingActivity(); + return; + } + + // Request permissions. + ActivityCompat.requestPermissions(getActivity(), arrPermissions, REQUEST_DANGEROUS_PERMISSIONS); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + boolean granted = true; + for (int i = 0; i < grantResults.length; i++) { // we nee all permissions granted + if (grantResults[i] != PackageManager.PERMISSION_GRANTED) + granted = false; + } + + switch (requestCode) { + case REQUEST_DANGEROUS_PERMISSIONS: + if (granted) + startAddScheduledRecordingActivity(); + else + Toast.makeText(getActivity(), getString(R.string.toast_permissions_denied), Toast.LENGTH_LONG).show(); + + break; + } + } + + private void startAddScheduledRecordingActivity() { + Intent intent = AddScheduledRecordingActivity.makeIntent(getActivity(), selectedDate.getTime()); + startActivityForResult(intent, ADD_SCHEDULED_RECORDING); + } + // After a new scheduled recording has been added, get all the recordings and refresh the layout. @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 23ed0d6b..05a9f352 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -21,6 +21,8 @@ Un\'altra registrazione è già pianificata per l\'orario selezionato! Errore nell\'eliminazione della registrazione pianificata! Elemento cancellato con successo + L\'app non funzionerà senza i permessi richiesti! + Registrazione... diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2d33de99..b0cab450 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -22,6 +22,7 @@ An error occured while saving the scheduled recording! An error occured while deleting the scheduled recording! Item successfully deleted + The app won\'t work without the requested permissions! diff --git a/others/notes b/others/notes index bc72c6a8..6c0cff4e 100644 --- a/others/notes +++ b/others/notes @@ -1,12 +1,15 @@ bug da sistemare: -- quando si imposta una registrazione schedulizzata bisogna controllare se ci sono i permessi - di scrittura -- quando si lancia una registrazione normale e vengono chiesti i permessi, una volta dati bisogna - far partire la registrazione -- lancio registrazione manuale e scatta una registrazione schedulizzata: cosa succede? -- quando termina una registrazione schedulizzata il programma si arresta (forse dipende dal toast - che non deve essere visualizzato ad app spenta) - miglioramento: se creassi un Service come classe astratta e due estensioni concrete per registrazioni normali e schedulizzate? - usare dependency injection per DBHelper con Dagger 2 - guardare il codice e cercare uso operatore new all’interno delle classi: si può sostituire con DI? + + +sistemati: +- quando termina una registrazione schedulizzata il programma si arresta (forse dipende dal toast + che non deve essere visualizzato ad app spenta) +- quando si imposta una registrazione schedulizzata bisogna controllare se ci sono i permessi + di scrittura +- quando si lancia una registrazione normale e vengono chiesti i permessi, una volta dati bisogna + far partire la registrazione +- lancio registrazione manuale e scatta una registrazione schedulizzata: cosa succede? \ No newline at end of file From 3c8e4c263c0e1d04e91a89eb57669729770253a2 Mon Sep 17 00:00:00 2001 From: iClaude Date: Fri, 6 Oct 2017 20:48:08 +0200 Subject: [PATCH 57/96] NDC - interface between Service and Activity simplified --- app/build.gradle | 1 + .../soundrecorder/RecordingService.java | 32 ++----- .../activities/MainActivity.java | 83 +++++++------------ .../fragments/RecordFragment.java | 23 ++--- 4 files changed, 46 insertions(+), 93 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 555e877c..15b25326 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -51,6 +51,7 @@ dependencies { compile "com.android.support:appcompat-v7:$libraryVersion" compile "com.android.support:cardview-v7:$libraryVersion" compile "com.android.support:recyclerview-v7:$libraryVersion" + compile "com.android.support:support-v13:$libraryVersion" // Testing. androidTestCompile('com.android.support.test:runner:0.5') { diff --git a/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java b/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java index 60e35143..02b7bcf5 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java +++ b/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java @@ -89,13 +89,15 @@ public IBinder onBind(Intent intent) { } /* - Interface used to communicate to a connected Activity: the seconds elapsed and the end - of a recording with file path. + Interface used to communicate to a connected Activity changes in the status of a + recording: + - recording started + - recording stopped (with file path) + - seconds elapsed */ public interface OnRecordingStatusChangedListener { void onRecordingStarted(); void onTimerChanged(int seconds); - void onRecordingStopped(String filePath); } @@ -105,21 +107,6 @@ public void setOnRecordingStatusChangedListener(OnRecordingStatusChangedListener this.onRecordingStatusChangedListener = onRecordingStatusChangedListener; } - /* - Interface used to communicate the start/stop of a scheduled recording to a connected - Activity, so that the UI can be updated accordingly. - */ - public interface OnScheduledRecordingListener { - void onScheduledRecordingStart(); - void onScheduledRecordingStop(); - } - - private OnScheduledRecordingListener onScheduledRecordingListener = null; - - public void setOnScheduledRecordingListener(OnScheduledRecordingListener listener) { - this.onScheduledRecordingListener = listener; - } - /* The following code implements a started Service. */ @@ -137,8 +124,6 @@ public int onStartCommand(Intent intent, int flags, int startId) { startService(ScheduledRecordingService.makeIntent(this, false)); if (!isRecording) { - if (onScheduledRecordingListener != null) // if an Activity is connected, inform it that a scheduled recording has started - onScheduledRecordingListener.onScheduledRecordingStart(); startRecording(duration); } } @@ -272,11 +257,8 @@ private void stopScheduledRecording() { // Stop recording as usual. stopRecording(); - /* If an Activity is connected, inform it that the scheduled recording has stopped, - otherwise stop the Service. */ - if (onScheduledRecordingListener != null) - onScheduledRecordingListener.onScheduledRecordingStop(); - else + // No Activity connected -> stop the Service. + if (onRecordingStatusChangedListener == null) stopSelf(); } diff --git a/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java b/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java index 728a3212..4aeb62ae 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java +++ b/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java @@ -23,7 +23,7 @@ import com.danielkim.soundrecorder.fragments.ScheduledRecordingsFragment; -public class MainActivity extends ActionBarActivity implements RecordingService.OnRecordingStatusChangedListener, RecordFragment.ServiceOperations { +public class MainActivity extends ActionBarActivity implements RecordFragment.ServiceOperations { private static final String TAG = "SCHEDULED_RECORDER_TAG"; @@ -133,7 +133,7 @@ protected void onStop() { if (serviceConnected) { Log.d(TAG, "MainActivity - call unbind from Service"); unbindService(serviceConnection); - if (!isRecording()) stopService(RecordingService.makeIntent(this)); + if (!isServiceRecording()) stopService(RecordingService.makeIntent(this)); recordingService = null; serviceConnected = false; if (recordFragment != null) { @@ -149,7 +149,7 @@ with RecordingService (the connection with the Service is managed by this Activi */ @Override public void requestStartRecording() { - if (recordingService != null && !isRecording()) { + if (recordingService != null && !isServiceRecording()) { recordingService.startRecording(0); } } @@ -168,7 +168,10 @@ public boolean isServiceConnected() { @Override public boolean isServiceRecording() { - return isRecording(); + if (recordingService != null) { + return recordingService.isRecording(); + } + return false; } /* @@ -184,15 +187,14 @@ public void onServiceConnected(ComponentName componentName, IBinder iBinder) { if (recordFragment != null) { recordFragment.serviceConnection(true); } - recordingService.setOnRecordingStatusChangedListener(MainActivity.this); - recordingService.setOnScheduledRecordingListener(onScheduledRecordingListener); + recordingService.setOnRecordingStatusChangedListener(onScheduledRecordingListener); } @Override public void onServiceDisconnected(ComponentName componentName) { Log.d(TAG, "MainActivity - Service disconnected"); recordingService.setOnRecordingStatusChangedListener(null); - recordingService.setOnScheduledRecordingListener(null); + recordingService.setOnRecordingStatusChangedListener(null); recordingService = null; serviceConnected = false; if (recordFragment != null) { @@ -201,63 +203,36 @@ public void onServiceDisconnected(ComponentName componentName) { } }; - // Is the connected Service currently recording? - private boolean isRecording() { - //noinspection SimplifiableIfStatement - if (recordingService != null) { - return recordingService.isRecording(); - } - return false; - } - /* Implementation of RecordingService.OnRecordingStatusChangedListener interface. - The Service uses this interface to communicate the status of the recording (started, - stopped and the seconds elapsed). + The Service uses this interface to communicate to the connected Activity that a + recording has started/stopped, and the seconds elapsed, so that the UI can be updated + accordingly. */ - - @Override - public void onRecordingStarted() { - if (recordFragment != null) - recordFragment.updateUI(true, null); - } - - // This method is called from a separate thread. - @Override - public void onTimerChanged(int seconds) { - if (recordFragment != null) { - runOnUiThread(new Runnable() { - @Override - public void run() { - recordFragment.timerChanged(seconds); - } - }); + private final RecordingService.OnRecordingStatusChangedListener onScheduledRecordingListener = new RecordingService.OnRecordingStatusChangedListener() { + @Override + public void onRecordingStarted() { + if (recordFragment != null) + recordFragment.recordingStarted(); } - } - - @Override - public void onRecordingStopped(String filePath) { - if (recordFragment != null) - recordFragment.updateUI(false, filePath); - - } - /* - Implementation of RecordingService.OnScheduledRecordingListener interface. - The Service uses this interface to communicate to the connected Activity that a - scheduled recording has started/stopped, so that the UI can be updated accordingly. - */ - private final RecordingService.OnScheduledRecordingListener onScheduledRecordingListener = new RecordingService.OnScheduledRecordingListener() { @Override - public void onScheduledRecordingStart() { + public void onRecordingStopped(String filePath) { if (recordFragment != null) - recordFragment.scheduledRecordingStarted(); + recordFragment.recordingStopped(filePath); } + // This method is called from a separate thread. @Override - public void onScheduledRecordingStop() { - if (recordFragment != null) - recordFragment.scheduledRecordingStopped(); + public void onTimerChanged(int seconds) { + if (recordFragment != null) { + runOnUiThread(new Runnable() { + @Override + public void run() { + recordFragment.timerChanged(seconds); + } + }); + } } }; diff --git a/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java b/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java index f1948b9e..a57e5b46 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java +++ b/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java @@ -7,7 +7,7 @@ import android.os.Bundle; import android.os.Environment; import android.support.annotation.NonNull; -import android.support.v4.app.ActivityCompat; +import android.support.v13.app.FragmentCompat; import android.support.v4.app.Fragment; import android.support.v4.content.ContextCompat; import android.util.Log; @@ -167,7 +167,7 @@ private void checkPermissions() { } // Request permissions. - ActivityCompat.requestPermissions(getActivity(), arrPermissions, REQUEST_DANGEROUS_PERMISSIONS); + FragmentCompat.requestPermissions(this, arrPermissions, REQUEST_DANGEROUS_PERMISSIONS); } @Override @@ -178,15 +178,10 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permis granted = false; } - switch (requestCode) { - case REQUEST_DANGEROUS_PERMISSIONS: - if (granted) - startStopRecording(); - else - Toast.makeText(getActivity(), getString(R.string.toast_permissions_denied), Toast.LENGTH_LONG).show(); - - break; - } + if (granted) + startStopRecording(); + else + Toast.makeText(getActivity(), getString(R.string.toast_permissions_denied), Toast.LENGTH_LONG).show(); } // Recording Start/Stop @@ -265,15 +260,15 @@ public void timerChanged(int seconds) { tvChronometer.setText(mTimerFormat.format(new Date(seconds * 1000L))); } - public void scheduledRecordingStarted() { + public void recordingStarted() { Log.d(TAG, "RecordFragment - scheduledRecordingStarted"); updateUI(true, null); isRecording = true; } - public void scheduledRecordingStopped() { + public void recordingStopped(String filePath) { Log.d(TAG, "RecordFragment - scheduledRecordingStopped"); - updateUI(false, null); + updateUI(false, filePath); isRecording = false; } From c7a206ea09655ae48cfb41d7ace10cfb094b41e2 Mon Sep 17 00:00:00 2001 From: iClaude Date: Mon, 9 Oct 2017 13:45:33 +0200 Subject: [PATCH 58/96] interfaces between RecordingService and Activity simplified --- .../com/danielkim/soundrecorder/fragments/RecordFragment.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java b/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java index a57e5b46..96a47c94 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java +++ b/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java @@ -7,7 +7,7 @@ import android.os.Bundle; import android.os.Environment; import android.support.annotation.NonNull; -import android.support.v13.app.FragmentCompat; +import android.support.v4.app.ActivityCompat; import android.support.v4.app.Fragment; import android.support.v4.content.ContextCompat; import android.util.Log; @@ -167,7 +167,7 @@ private void checkPermissions() { } // Request permissions. - FragmentCompat.requestPermissions(this, arrPermissions, REQUEST_DANGEROUS_PERMISSIONS); + ActivityCompat.requestPermissions(getActivity(), arrPermissions, REQUEST_DANGEROUS_PERMISSIONS); } @Override From 3a35216fc704906e5cdaabdb852f0eb971d00ca8 Mon Sep 17 00:00:00 2001 From: iClaude Date: Mon, 9 Oct 2017 14:04:32 +0200 Subject: [PATCH 59/96] Marshmallow permissions requested on a Fragment basis --- .../activities/MainActivity.java | 8 ++++---- .../fragments/FileViewerFragment.java | 2 +- .../fragments/RecordFragment.java | 6 +++--- .../ScheduledRecordingsFragment.java | 19 +++++++------------ 4 files changed, 15 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java b/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java index 4aeb62ae..ecc11d99 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java +++ b/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java @@ -1,12 +1,12 @@ package com.danielkim.soundrecorder.activities; +import android.app.Fragment; +import android.app.FragmentManager; import android.content.ComponentName; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentPagerAdapter; +import android.support.v13.app.FragmentPagerAdapter; import android.support.v4.view.ViewPager; import android.support.v7.app.ActionBarActivity; import android.support.v7.widget.Toolbar; @@ -39,7 +39,7 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_main); ViewPager pager = (ViewPager) findViewById(R.id.pager); - pager.setAdapter(new MyAdapter(getSupportFragmentManager())); + pager.setAdapter(new MyAdapter(getFragmentManager())); PagerSlidingTabStrip tabs = (PagerSlidingTabStrip) findViewById(R.id.tabs); tabs.setViewPager(pager); diff --git a/app/src/main/java/com/danielkim/soundrecorder/fragments/FileViewerFragment.java b/app/src/main/java/com/danielkim/soundrecorder/fragments/FileViewerFragment.java index bf29e7f0..f41710c9 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/fragments/FileViewerFragment.java +++ b/app/src/main/java/com/danielkim/soundrecorder/fragments/FileViewerFragment.java @@ -1,8 +1,8 @@ package com.danielkim.soundrecorder.fragments; +import android.app.Fragment; import android.os.Bundle; import android.os.FileObserver; -import android.support.v4.app.Fragment; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; diff --git a/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java b/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java index 96a47c94..4d3b5b99 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java +++ b/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java @@ -1,14 +1,14 @@ package com.danielkim.soundrecorder.fragments; import android.Manifest; +import android.app.Fragment; import android.content.Context; import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.support.annotation.NonNull; -import android.support.v4.app.ActivityCompat; -import android.support.v4.app.Fragment; +import android.support.v13.app.FragmentCompat; import android.support.v4.content.ContextCompat; import android.util.Log; import android.view.LayoutInflater; @@ -167,7 +167,7 @@ private void checkPermissions() { } // Request permissions. - ActivityCompat.requestPermissions(getActivity(), arrPermissions, REQUEST_DANGEROUS_PERMISSIONS); + FragmentCompat.requestPermissions(this, arrPermissions, REQUEST_DANGEROUS_PERMISSIONS); } @Override diff --git a/app/src/main/java/com/danielkim/soundrecorder/fragments/ScheduledRecordingsFragment.java b/app/src/main/java/com/danielkim/soundrecorder/fragments/ScheduledRecordingsFragment.java index 6ac689c4..29fbac68 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/fragments/ScheduledRecordingsFragment.java +++ b/app/src/main/java/com/danielkim/soundrecorder/fragments/ScheduledRecordingsFragment.java @@ -7,6 +7,7 @@ import android.Manifest; import android.app.Activity; import android.app.AlertDialog; +import android.app.Fragment; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; @@ -14,8 +15,7 @@ import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.v4.app.ActivityCompat; -import android.support.v4.app.Fragment; +import android.support.v13.app.FragmentCompat; import android.support.v4.content.ContextCompat; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; @@ -238,7 +238,7 @@ private void checkPermissions() { } // Request permissions. - ActivityCompat.requestPermissions(getActivity(), arrPermissions, REQUEST_DANGEROUS_PERMISSIONS); + FragmentCompat.requestPermissions(this, arrPermissions, REQUEST_DANGEROUS_PERMISSIONS); } @Override @@ -249,15 +249,10 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permis granted = false; } - switch (requestCode) { - case REQUEST_DANGEROUS_PERMISSIONS: - if (granted) - startAddScheduledRecordingActivity(); - else - Toast.makeText(getActivity(), getString(R.string.toast_permissions_denied), Toast.LENGTH_LONG).show(); - - break; - } + if (granted) + startAddScheduledRecordingActivity(); + else + Toast.makeText(getActivity(), getString(R.string.toast_permissions_denied), Toast.LENGTH_LONG).show(); } private void startAddScheduledRecordingActivity() { From 5197aeaff9d5e7934cdad8827b77b0544b6c4943 Mon Sep 17 00:00:00 2001 From: iClaude Date: Mon, 9 Oct 2017 14:12:43 +0200 Subject: [PATCH 60/96] migrating from v4 to ordinary Fragments --- .../com/danielkim/soundrecorder/activities/MainActivity.java | 2 +- .../danielkim/soundrecorder/adapters/FileViewerAdapter.java | 4 ++-- .../danielkim/soundrecorder/fragments/LicensesFragment.java | 2 +- .../danielkim/soundrecorder/fragments/PlaybackFragment.java | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java b/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java index ecc11d99..19fc471d 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java +++ b/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java @@ -72,7 +72,7 @@ public boolean onOptionsItemSelected(MenuItem item) { private void openLicenses() { LicensesFragment licensesFragment = new LicensesFragment(); - licensesFragment.show(getSupportFragmentManager().beginTransaction(), "dialog_licenses"); + licensesFragment.show(getFragmentManager().beginTransaction(), "dialog_licenses"); } public class MyAdapter extends FragmentPagerAdapter { diff --git a/app/src/main/java/com/danielkim/soundrecorder/adapters/FileViewerAdapter.java b/app/src/main/java/com/danielkim/soundrecorder/adapters/FileViewerAdapter.java index 2f42df00..f0c9fd73 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/adapters/FileViewerAdapter.java +++ b/app/src/main/java/com/danielkim/soundrecorder/adapters/FileViewerAdapter.java @@ -1,13 +1,13 @@ package com.danielkim.soundrecorder.adapters; import android.app.AlertDialog; +import android.app.FragmentTransaction; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; import android.os.Environment; import android.support.v4.app.FragmentActivity; -import android.support.v4.app.FragmentTransaction; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.text.format.DateUtils; @@ -80,7 +80,7 @@ public void onClick(View view) { new PlaybackFragment().newInstance(getItem(holder.getPosition())); FragmentTransaction transaction = ((FragmentActivity) mContext) - .getSupportFragmentManager() + .getFragmentManager() .beginTransaction(); playbackFragment.show(transaction, "dialog_playback"); diff --git a/app/src/main/java/com/danielkim/soundrecorder/fragments/LicensesFragment.java b/app/src/main/java/com/danielkim/soundrecorder/fragments/LicensesFragment.java index a93dbded..c58866c6 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/fragments/LicensesFragment.java +++ b/app/src/main/java/com/danielkim/soundrecorder/fragments/LicensesFragment.java @@ -2,8 +2,8 @@ import android.app.AlertDialog; import android.app.Dialog; +import android.app.DialogFragment; import android.os.Bundle; -import android.support.v4.app.DialogFragment; import android.view.LayoutInflater; import android.view.View; diff --git a/app/src/main/java/com/danielkim/soundrecorder/fragments/PlaybackFragment.java b/app/src/main/java/com/danielkim/soundrecorder/fragments/PlaybackFragment.java index 09f12135..5617d0de 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/fragments/PlaybackFragment.java +++ b/app/src/main/java/com/danielkim/soundrecorder/fragments/PlaybackFragment.java @@ -2,13 +2,13 @@ import android.app.AlertDialog; import android.app.Dialog; +import android.app.DialogFragment; import android.graphics.ColorFilter; import android.graphics.LightingColorFilter; import android.media.MediaPlayer; import android.os.Bundle; import android.os.Handler; import android.support.annotation.NonNull; -import android.support.v4.app.DialogFragment; import android.util.Log; import android.view.View; import android.view.Window; From 809e0f055fd58f60d07d8b93a9f6451787fc58e6 Mon Sep 17 00:00:00 2001 From: iClaude Date: Mon, 9 Oct 2017 14:41:57 +0200 Subject: [PATCH 61/96] check Marshmallow+ permissions for scheduled recordings --- .../soundrecorder/RecordingService.java | 16 +++++++++++++++- .../soundrecorder/fragments/RecordFragment.java | 4 ++-- others/notes | 8 -------- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java b/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java index 02b7bcf5..e7372864 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java +++ b/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java @@ -5,16 +5,19 @@ package com.danielkim.soundrecorder; +import android.Manifest; import android.app.Notification; import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.media.MediaRecorder; import android.os.Binder; import android.os.Environment; import android.os.IBinder; import android.support.v4.app.NotificationCompat; +import android.support.v4.content.ContextCompat; import android.util.Log; import com.danielkim.soundrecorder.activities.MainActivity; @@ -123,7 +126,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { mDatabase.removeScheduledRecording(item.getId()); startService(ScheduledRecordingService.makeIntent(this, false)); - if (!isRecording) { + if (!isRecording && hasPermissions()) { startRecording(duration); } } @@ -279,4 +282,15 @@ private Notification createNotification() { public boolean isRecording() { return isRecording; } + + /* + For Marshmallow+ check if we have the necessary permissions. This method is called for + scheduled recordings because the use might deny the permissions after a scheduled + recording has already been set. + */ + private boolean hasPermissions() { + boolean writePerm = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; + boolean audioPerm = ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED; + return writePerm && audioPerm; + } } diff --git a/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java b/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java index 4d3b5b99..f30abbf6 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java +++ b/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java @@ -261,13 +261,13 @@ public void timerChanged(int seconds) { } public void recordingStarted() { - Log.d(TAG, "RecordFragment - scheduledRecordingStarted"); + Log.d(TAG, "RecordFragment - recording started"); updateUI(true, null); isRecording = true; } public void recordingStopped(String filePath) { - Log.d(TAG, "RecordFragment - scheduledRecordingStopped"); + Log.d(TAG, "RecordFragment - recording stopped"); updateUI(false, filePath); isRecording = false; } diff --git a/others/notes b/others/notes index 6c0cff4e..7e0ee8cd 100644 --- a/others/notes +++ b/others/notes @@ -5,11 +5,3 @@ bug da sistemare: - guardare il codice e cercare uso operatore new all’interno delle classi: si può sostituire con DI? -sistemati: -- quando termina una registrazione schedulizzata il programma si arresta (forse dipende dal toast - che non deve essere visualizzato ad app spenta) -- quando si imposta una registrazione schedulizzata bisogna controllare se ci sono i permessi - di scrittura -- quando si lancia una registrazione normale e vengono chiesti i permessi, una volta dati bisogna - far partire la registrazione -- lancio registrazione manuale e scatta una registrazione schedulizzata: cosa succede? \ No newline at end of file From 356b761e032c73d9b2e18e71c77d9455b19f6992 Mon Sep 17 00:00:00 2001 From: iClaude Date: Mon, 9 Oct 2017 15:11:59 +0200 Subject: [PATCH 62/96] code cleanup --- .../danielkim/soundrecorder/RecordingService.java | 3 +-- .../soundrecorder/activities/MainActivity.java | 9 +++------ .../soundrecorder/fragments/RecordFragment.java | 13 ++++++------- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java b/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java index e7372864..40a1b2be 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java +++ b/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java @@ -72,8 +72,7 @@ public static Intent makeIntent(Context context, boolean activityStarter) { Other convenient method used to retrieve an empty Intent (i.e to stop this Service). */ public static Intent makeIntent(Context context) { - Intent intent = new Intent(context, RecordingService.class); - return intent; + return new Intent(context, RecordingService.class); } /* diff --git a/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java b/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java index 19fc471d..c27d6b69 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java +++ b/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java @@ -8,7 +8,7 @@ import android.os.IBinder; import android.support.v13.app.FragmentPagerAdapter; import android.support.v4.view.ViewPager; -import android.support.v7.app.ActionBarActivity; +import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.util.Log; import android.view.Menu; @@ -23,7 +23,7 @@ import com.danielkim.soundrecorder.fragments.ScheduledRecordingsFragment; -public class MainActivity extends ActionBarActivity implements RecordFragment.ServiceOperations { +public class MainActivity extends AppCompatActivity implements RecordFragment.ServiceOperations { private static final String TAG = "SCHEDULED_RECORDER_TAG"; @@ -168,10 +168,7 @@ public boolean isServiceConnected() { @Override public boolean isServiceRecording() { - if (recordingService != null) { - return recordingService.isRecording(); - } - return false; + return recordingService != null && recordingService.isRecording(); } /* diff --git a/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java b/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java index f30abbf6..bd16d1b2 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java +++ b/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java @@ -102,7 +102,6 @@ public RecordFragment() { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - int position = getArguments().getInt(ARG_POSITION); } @Override @@ -116,8 +115,8 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, mRecordingPrompt = (TextView) recordView.findViewById(R.id.recording_status_text); mRecordButton = (FloatingActionButton) recordView.findViewById(R.id.btnRecord); - mRecordButton.setColorNormal(getResources().getColor(R.color.primary)); - mRecordButton.setColorPressed(getResources().getColor(R.color.primary_dark)); + mRecordButton.setColorNormal(ContextCompat.getColor(getActivity(), R.color.primary)); + mRecordButton.setColorPressed(ContextCompat.getColor(getActivity(), R.color.primary_dark)); mRecordButton.setEnabled(serviceOperations.isServiceConnected()); mRecordButton.setOnClickListener(new View.OnClickListener() { @Override @@ -135,7 +134,7 @@ public void onClick(View v) { mPauseButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - onPauseRecord(mPauseRecording); + //onPauseRecord(mPauseRecording); mPauseRecording = !mPauseRecording; } }); @@ -212,7 +211,7 @@ private void startStopRecording() { } } - public void updateUI(boolean recording, String filePath) { + private void updateUI(boolean recording, String filePath) { if (recording) { mRecordButton.setImageResource(R.drawable.ic_media_stop); mRecordingPrompt.setText(getString(R.string.record_in_progress) + "..."); @@ -227,9 +226,9 @@ public void updateUI(boolean recording, String filePath) { } //TODO: implement pause recording - private void onPauseRecord(boolean pause) { +/* private void onPauseRecord(boolean pause) { - } + }*/ /* When the Activity establishes the connection with the Service, it informs this Fragment From 80c9f944124cdad2da9e1dcf302a32bb360d1513 Mon Sep 17 00:00:00 2001 From: iClaude Date: Mon, 9 Oct 2017 15:21:56 +0200 Subject: [PATCH 63/96] new things to do --- others/notes | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/others/notes b/others/notes index 7e0ee8cd..d3f9d5cd 100644 --- a/others/notes +++ b/others/notes @@ -1,7 +1,4 @@ -bug da sistemare: -- miglioramento: se creassi un Service come classe astratta e due estensioni concrete per - registrazioni normali e schedulizzate? +da sistemare: - usare dependency injection per DBHelper con Dagger 2 - guardare il codice e cercare uso operatore new all’interno delle classi: si può sostituire con DI? - - +- eliminare tutti i log \ No newline at end of file From d682224136fb5c47c8a2d867da18eee583d8fecb Mon Sep 17 00:00:00 2001 From: iClaude Date: Mon, 9 Oct 2017 19:16:37 +0200 Subject: [PATCH 64/96] using dependency injection with Dagger2 for database management --- app/build.gradle | 8 +++++ app/src/main/AndroidManifest.xml | 1 + .../soundrecorder/RecordingService.java | 17 ++++++---- .../ScheduledRecordingService.java | 11 +++++-- .../AddScheduledRecordingActivity.java | 9 ++++- .../adapters/FileViewerAdapter.java | 18 ++++++---- .../soundrecorder/didagger2/App.java | 33 +++++++++++++++++++ .../soundrecorder/didagger2/AppComponent.java | 33 +++++++++++++++++++ .../soundrecorder/didagger2/AppModule.java | 31 +++++++++++++++++ .../didagger2/DBHelperModule.java | 29 ++++++++++++++++ .../ScheduledRecordingsFragment.java | 14 ++++++-- others/notes | 4 ++- 12 files changed, 189 insertions(+), 19 deletions(-) create mode 100644 app/src/main/java/com/danielkim/soundrecorder/didagger2/App.java create mode 100644 app/src/main/java/com/danielkim/soundrecorder/didagger2/AppComponent.java create mode 100644 app/src/main/java/com/danielkim/soundrecorder/didagger2/AppModule.java create mode 100644 app/src/main/java/com/danielkim/soundrecorder/didagger2/DBHelperModule.java diff --git a/app/build.gradle b/app/build.gradle index 15b25326..d3cc3262 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -45,6 +45,7 @@ android { ext { libraryVersion = '25.1.1' + daggerVersion = '2.11-rc2' } dependencies { @@ -69,4 +70,11 @@ dependencies { compile 'junit:junit:4.12' compile 'com.android.support:support-v4:25.3.1' testCompile 'org.robolectric:robolectric:3.3.2' + + // Dagger2. + compile "com.google.dagger:dagger:$daggerVersion" + annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion" + compile "com.google.dagger:dagger-android-support:$daggerVersion" + provided 'javax.annotation:jsr250-api:1.0' + } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 47f9a783..4d5ae5a9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -9,6 +9,7 @@ { - private final DBHelper dbHelper = new DBHelper(AddScheduledRecordingActivity.this); protected Integer doInBackground(Void... params) { long startLong = new GregorianCalendar(yearStart, monthStart, dayStart, hourStart, minuteStart).getTimeInMillis(); diff --git a/app/src/main/java/com/danielkim/soundrecorder/adapters/FileViewerAdapter.java b/app/src/main/java/com/danielkim/soundrecorder/adapters/FileViewerAdapter.java index f0c9fd73..ce1d6048 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/adapters/FileViewerAdapter.java +++ b/app/src/main/java/com/danielkim/soundrecorder/adapters/FileViewerAdapter.java @@ -22,6 +22,7 @@ import com.danielkim.soundrecorder.R; import com.danielkim.soundrecorder.RecordingItem; import com.danielkim.soundrecorder.database.DBHelper; +import com.danielkim.soundrecorder.didagger2.App; import com.danielkim.soundrecorder.fragments.PlaybackFragment; import com.danielkim.soundrecorder.listeners.OnDatabaseChangedListener; @@ -29,6 +30,8 @@ import java.util.ArrayList; import java.util.concurrent.TimeUnit; +import javax.inject.Inject; + /** * Created by Daniel on 12/29/2014. */ @@ -37,7 +40,8 @@ public class FileViewerAdapter extends RecyclerView.Adapter @@ -61,6 +64,9 @@ public class ScheduledRecordingsFragment extends Fragment implements ScheduledRe private TextView tvMonth; private TextView tvDate; + @Inject + DBHelper dbHelper; + private RecyclerView.Adapter adapter; private List scheduledRecordings; private Date selectedDate = new Date(System.currentTimeMillis()); @@ -74,6 +80,12 @@ public static ScheduledRecordingsFragment newInstance(int position) { return f; } + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + App.getComponent().inject(this); + } + @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { @@ -139,7 +151,6 @@ private void displayScheduledRecordings(Date date) { // Retrieve all scheduled recordings in a separate thread. private class GetScheduledRecordingsTask extends AsyncTask> { - private final DBHelper dbHelper = new DBHelper(getActivity()); public GetScheduledRecordingsTask() { } @@ -194,7 +205,6 @@ public void onClick(DialogInterface dialog, int id) { // Retrieve all scheduled recordings in a separate thread. private class DeleteItemTask extends AsyncTask { - private final DBHelper dbHelper = new DBHelper(getActivity()); protected Integer doInBackground(Long... params) { return dbHelper.removeScheduledRecording(params[0]); diff --git a/others/notes b/others/notes index d3f9d5cd..5389300d 100644 --- a/others/notes +++ b/others/notes @@ -1,4 +1,6 @@ da sistemare: -- usare dependency injection per DBHelper con Dagger 2 +- usare dependency injection per DBHelper con Dagger 2: + a) importare librerie e testare codice + b) e per i test? - guardare il codice e cercare uso operatore new all’interno delle classi: si può sostituire con DI? - eliminare tutti i log \ No newline at end of file From a0fcf06b7ccd7c990413b88a9fbffbec6613d273 Mon Sep 17 00:00:00 2001 From: iClaude Date: Wed, 11 Oct 2017 15:02:11 +0200 Subject: [PATCH 65/96] bug in the disconnection of the Activity from Service fixed --- .../soundrecorder/RecordingService.java | 24 +++++++------------ .../activities/MainActivity.java | 2 +- .../fragments/RecordFragment.java | 3 ++- others/notes | 7 +++--- 4 files changed, 15 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java b/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java index 9fb80093..bb8081a4 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java +++ b/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java @@ -177,7 +177,7 @@ public void startRecording(int duration) { // Called only if a max duration has been set (scheduled recordings). public void onInfo(MediaRecorder mediaRecorder, int what, int extra) { if (what == MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) { - stopScheduledRecording(); + stopRecording(); } } }); @@ -242,11 +242,6 @@ public void stopRecording() { onRecordingStatusChangedListener.onRecordingStopped(mFilePath); } - // Stop timer. - if (mIncrementTimerTask != null) { - mIncrementTimerTask.cancel(); - mIncrementTimerTask = null; - } // Save the recording data in the database. try { @@ -255,18 +250,17 @@ public void stopRecording() { Log.e(TAG, "exception", e); } - stopForeground(true); - } - - // Specific to scheduled recordings. - private void stopScheduledRecording() { - Log.d(TAG, "RecordingService - stopScheduledRecording"); - // Stop recording as usual. - stopRecording(); + // Stop timer. + if (mIncrementTimerTask != null) { + mIncrementTimerTask.cancel(); + mIncrementTimerTask = null; + } - // No Activity connected -> stop the Service. + // No Activity connected -> stop the Service (scheduled recording). if (onRecordingStatusChangedListener == null) stopSelf(); + + stopForeground(true); } private Notification createNotification() { diff --git a/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java b/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java index c27d6b69..2c6468d1 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java +++ b/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java @@ -134,6 +134,7 @@ protected void onStop() { Log.d(TAG, "MainActivity - call unbind from Service"); unbindService(serviceConnection); if (!isServiceRecording()) stopService(RecordingService.makeIntent(this)); + recordingService.setOnRecordingStatusChangedListener(null); recordingService = null; serviceConnected = false; if (recordFragment != null) { @@ -191,7 +192,6 @@ public void onServiceConnected(ComponentName componentName, IBinder iBinder) { public void onServiceDisconnected(ComponentName componentName) { Log.d(TAG, "MainActivity - Service disconnected"); recordingService.setOnRecordingStatusChangedListener(null); - recordingService.setOnRecordingStatusChangedListener(null); recordingService = null; serviceConnected = false; if (recordFragment != null) { diff --git a/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java b/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java index bd16d1b2..11ea5707 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java +++ b/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java @@ -256,7 +256,8 @@ private void checkRecording() { } public void timerChanged(int seconds) { - tvChronometer.setText(mTimerFormat.format(new Date(seconds * 1000L))); + if (isRecording) + tvChronometer.setText(mTimerFormat.format(new Date(seconds * 1000L))); } public void recordingStarted() { diff --git a/others/notes b/others/notes index 5389300d..55e624b8 100644 --- a/others/notes +++ b/others/notes @@ -1,6 +1,5 @@ da sistemare: -- usare dependency injection per DBHelper con Dagger 2: - a) importare librerie e testare codice - b) e per i test? -- guardare il codice e cercare uso operatore new all’interno delle classi: si può sostituire con DI? +- bug scheduled recording: registra ma non salva nel database +- rivedere test +- creare nuovi functional test - eliminare tutti i log \ No newline at end of file From 2329410cf95c365c92232ad40cd0b4c2b87b9049 Mon Sep 17 00:00:00 2001 From: iClaude Date: Wed, 11 Oct 2017 15:25:51 +0200 Subject: [PATCH 66/96] bug with incorrect context fixed --- .../danielkim/soundrecorder/ScheduledRecordingService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/danielkim/soundrecorder/ScheduledRecordingService.java b/app/src/main/java/com/danielkim/soundrecorder/ScheduledRecordingService.java index a58abe62..f367a1c6 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/ScheduledRecordingService.java +++ b/app/src/main/java/com/danielkim/soundrecorder/ScheduledRecordingService.java @@ -123,8 +123,8 @@ protected void resetAlarmManager() { protected void scheduleNextRecording() { ScheduledRecordingItem item = dbHelper.getNextScheduledRecording(); if (item != null) { - Intent intent = RecordingService.makeIntent(getApplicationContext(), false); - PendingIntent pendingIntent = PendingIntent.getService(getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + Intent intent = RecordingService.makeIntent(context, false); + PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR2) { // up to API 18 alarmManager.set(AlarmManager.RTC_WAKEUP, item.getStart(), pendingIntent); } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2 && Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) { // API 19-22 From 7bba7a8c00430655a0cfc84ed6d954ad1d174c0a Mon Sep 17 00:00:00 2001 From: iClaude Date: Wed, 11 Oct 2017 20:30:00 +0200 Subject: [PATCH 67/96] working on Espresso tests for MainActivity --- app/build.gradle | 27 +++++--- .../MainActivityEspressoTest.java | 63 +++++++++++++++++++ build.gradle | 8 +++ 3 files changed, 89 insertions(+), 9 deletions(-) create mode 100644 app/src/androidTest/java/com/danielkim/soundrecorder/MainActivityEspressoTest.java diff --git a/app/build.gradle b/app/build.gradle index d3cc3262..57ddf7ae 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -44,7 +44,7 @@ android { } ext { - libraryVersion = '25.1.1' + libraryVersion = '25.3.1' daggerVersion = '2.11-rc2' } @@ -53,22 +53,31 @@ dependencies { compile "com.android.support:cardview-v7:$libraryVersion" compile "com.android.support:recyclerview-v7:$libraryVersion" compile "com.android.support:support-v13:$libraryVersion" + compile "com.android.support:support-v4:$libraryVersion" + + compile 'com.android.support.constraint:constraint-layout:1.0.2' + compile 'com.melnykov:floatingactionbutton:1.1.0' + compile 'com.jpardogo.materialtabstrip:library:1.0.6' + compile 'com.github.sundeepk:compact-calendar-view:2.0.2.2' // Testing. - androidTestCompile('com.android.support.test:runner:0.5') { + androidTestCompile('com.android.support.test:runner:1.0.1') { exclude module: 'support-annotations' } - // Use JUnit 4 rules for this dependency. - androidTestCompile('com.android.support.test:rules:0.5') { + androidTestCompile('com.android.support.test:rules:1.0.1') { exclude module: 'support-annotations' } - compile 'com.android.support.constraint:constraint-layout:1.0.2' - compile 'com.melnykov:floatingactionbutton:1.1.0' - compile 'com.jpardogo.materialtabstrip:library:1.0.6' - compile 'com.github.sundeepk:compact-calendar-view:2.0.2.2' + androidTestCompile 'com.android.support.test.espresso:espresso-core:3.0.1' + androidTestCompile('com.android.support.test.espresso:espresso-contrib:3.0.1') { + exclude module: 'support-annotations' + exclude module: 'support-v4' + exclude module: 'support-v13' + exclude module: 'recyclerview-v7' + exclude module: 'design' + } + compile 'com.android.support:support-annotations:24.2.0' compile 'junit:junit:4.12' - compile 'com.android.support:support-v4:25.3.1' testCompile 'org.robolectric:robolectric:3.3.2' // Dagger2. diff --git a/app/src/androidTest/java/com/danielkim/soundrecorder/MainActivityEspressoTest.java b/app/src/androidTest/java/com/danielkim/soundrecorder/MainActivityEspressoTest.java new file mode 100644 index 00000000..fd0c6666 --- /dev/null +++ b/app/src/androidTest/java/com/danielkim/soundrecorder/MainActivityEspressoTest.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2017. This code was written by iClaude. All rights reserved. + */ + +package com.danielkim.soundrecorder; + +import android.support.test.filters.LargeTest; +import android.support.test.rule.ActivityTestRule; +import android.support.test.runner.AndroidJUnit4; + +import com.danielkim.soundrecorder.activities.MainActivity; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.contrib.RecyclerViewActions.scrollToPosition; +import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; +import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; + +/** + * Created by iClaude on 11/10/2017. + */ + +@RunWith(AndroidJUnit4.class) +@LargeTest +public class MainActivityEspressoTest { + + @Rule + public ActivityTestRule mActivityRule = new ActivityTestRule<>(MainActivity.class); + + @Test + public void startAndStopRecording() { + String recording = mActivityRule.getActivity().getResources().getString(R.string.record_in_progress); + String prompt = mActivityRule.getActivity().getResources().getString(R.string.record_prompt); + + // Start recording. + onView(withId(R.id.btnRecord)).perform(click()); + try { + Thread.sleep(3000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + onView(withId(R.id.recording_status_text)).check(matches(withText(containsString(recording)))); + onView(withId(R.id.tvChronometer)).check(matches(withText(not(containsString("00:00"))))); + + // Stop recording. + onView(withId(R.id.btnRecord)).perform(click()); + onView(withId(R.id.recording_status_text)).check(matches(withText(containsString(prompt)))); + onView(withId(R.id.tvChronometer)).check(matches(withText(containsString("00:00")))); + + // Check that the recording is added to FileViewerFragment. + String defaultFileName = mActivityRule.getActivity().getResources().getString(R.string.default_file_name); + onView(withId(R.id.recyclerView)).perform(scrollToPosition(0)); + onView(withText(containsString(defaultFileName))).check(matches(isDisplayed())); + } +} diff --git a/build.gradle b/build.gradle index 5224375d..a9bce6ee 100644 --- a/build.gradle +++ b/build.gradle @@ -19,5 +19,13 @@ buildscript { allprojects { repositories { jcenter() + maven { + url "https://maven.google.com" + } + } + + configurations.all { + resolutionStrategy.force 'com.android.support:support-annotations:25.3.1' + resolutionStrategy.force 'com.google.code.findbugs:jsr305:3.0.1' } } From d3143f6e16743f4135e8e015dfea8c8580f0a255 Mon Sep 17 00:00:00 2001 From: iClaude Date: Wed, 18 Oct 2017 13:04:54 +0200 Subject: [PATCH 68/96] things to do --- others/notes | 1 - 1 file changed, 1 deletion(-) diff --git a/others/notes b/others/notes index 55e624b8..66dada95 100644 --- a/others/notes +++ b/others/notes @@ -1,5 +1,4 @@ da sistemare: -- bug scheduled recording: registra ma non salva nel database - rivedere test - creare nuovi functional test - eliminare tutti i log \ No newline at end of file From 676f42e2074a64e4e0d658cd879a010fcb9541ec Mon Sep 17 00:00:00 2001 From: iClaude Date: Wed, 18 Oct 2017 14:30:15 +0200 Subject: [PATCH 69/96] Espresso test: start and stop recording: completed --- app/proguard-rules.pro | 3 +++ .../soundrecorder/MainActivityEspressoTest.java | 14 ++++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 14857155..eca1ac79 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -9,6 +9,9 @@ # Add any project specific keep options here: +-keep public interface android.support.test.internal.runner.tracker.UsageTracker {*;} + + # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: diff --git a/app/src/androidTest/java/com/danielkim/soundrecorder/MainActivityEspressoTest.java b/app/src/androidTest/java/com/danielkim/soundrecorder/MainActivityEspressoTest.java index fd0c6666..f8a621b7 100644 --- a/app/src/androidTest/java/com/danielkim/soundrecorder/MainActivityEspressoTest.java +++ b/app/src/androidTest/java/com/danielkim/soundrecorder/MainActivityEspressoTest.java @@ -16,9 +16,9 @@ import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.action.ViewActions.swipeLeft; import static android.support.test.espresso.assertion.ViewAssertions.matches; -import static android.support.test.espresso.contrib.RecyclerViewActions.scrollToPosition; -import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; +import static android.support.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition; import static android.support.test.espresso.matcher.ViewMatchers.withId; import static android.support.test.espresso.matcher.ViewMatchers.withText; import static org.hamcrest.Matchers.containsString; @@ -35,6 +35,11 @@ public class MainActivityEspressoTest { @Rule public ActivityTestRule mActivityRule = new ActivityTestRule<>(MainActivity.class); + /* + Check that: + - when I click on the start/stop record button the UI changes correctly + - when I stop recording the new recording is added to the file viewer Fragment + */ @Test public void startAndStopRecording() { String recording = mActivityRule.getActivity().getResources().getString(R.string.record_in_progress); @@ -57,7 +62,8 @@ public void startAndStopRecording() { // Check that the recording is added to FileViewerFragment. String defaultFileName = mActivityRule.getActivity().getResources().getString(R.string.default_file_name); - onView(withId(R.id.recyclerView)).perform(scrollToPosition(0)); - onView(withText(containsString(defaultFileName))).check(matches(isDisplayed())); + onView(withId(R.id.pager)).perform(swipeLeft()); + onView(withId(R.id.recyclerView)).perform(actionOnItemAtPosition(0, click())); + onView(withId(R.id.file_name_text_view)).check(matches(withText(containsString(defaultFileName)))); } } From f6e421ad90aedbc0655f1eaad7bbe1ce7b1f2889 Mon Sep 17 00:00:00 2001 From: iClaude Date: Wed, 18 Oct 2017 16:31:03 +0200 Subject: [PATCH 70/96] NDC - working on tests about Activity lifecycle and Service connection --- .../MainActivityEspressoTest.java | 80 ++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/app/src/androidTest/java/com/danielkim/soundrecorder/MainActivityEspressoTest.java b/app/src/androidTest/java/com/danielkim/soundrecorder/MainActivityEspressoTest.java index f8a621b7..504c7249 100644 --- a/app/src/androidTest/java/com/danielkim/soundrecorder/MainActivityEspressoTest.java +++ b/app/src/androidTest/java/com/danielkim/soundrecorder/MainActivityEspressoTest.java @@ -14,6 +14,7 @@ import org.junit.Test; import org.junit.runner.RunWith; +import static android.support.test.InstrumentationRegistry.getInstrumentation; import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.action.ViewActions.click; import static android.support.test.espresso.action.ViewActions.swipeLeft; @@ -36,7 +37,7 @@ public class MainActivityEspressoTest { public ActivityTestRule mActivityRule = new ActivityTestRule<>(MainActivity.class); /* - Check that: + Checks that: - when I click on the start/stop record button the UI changes correctly - when I stop recording the new recording is added to the file viewer Fragment */ @@ -66,4 +67,81 @@ public void startAndStopRecording() { onView(withId(R.id.recyclerView)).perform(actionOnItemAtPosition(0, click())); onView(withId(R.id.file_name_text_view)).check(matches(withText(containsString(defaultFileName)))); } + + /* + Checks that: + - when I stop the Activity while recording the recording continues + - when I start the Activity again the UI shows the ongoing recording correctly + */ + @Test + public void stopActivityWhileRecording() { + String recording = mActivityRule.getActivity().getResources().getString(R.string.record_in_progress); + String prompt = mActivityRule.getActivity().getResources().getString(R.string.record_prompt); + + onView(withId(R.id.btnRecord)).perform(click()); + try { + Thread.sleep(3000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + MainActivity activity = mActivityRule.getActivity(); + getInstrumentation().runOnMainSync(new Runnable() { + @Override + public void run() { + getInstrumentation().callActivityOnPause(activity); + getInstrumentation().callActivityOnStop(activity); + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + getInstrumentation().callActivityOnRestart(activity); + getInstrumentation().callActivityOnStart(activity); + getInstrumentation().callActivityOnResume(activity); + } + }); + + onView(withId(R.id.recording_status_text)).check(matches(withText(containsString(recording)))); + onView(withId(R.id.tvChronometer)).check(matches(withText(not(containsString("00:00"))))); + + onView(withId(R.id.btnRecord)).perform(click()); + onView(withId(R.id.recording_status_text)).check(matches(withText(containsString(prompt)))); + onView(withId(R.id.tvChronometer)).check(matches(withText(containsString("00:00")))); + } + + @Test + public void destroyActivityWhileRecording() { + String recording = mActivityRule.getActivity().getResources().getString(R.string.record_in_progress); + String prompt = mActivityRule.getActivity().getResources().getString(R.string.record_prompt); + + onView(withId(R.id.btnRecord)).perform(click()); + try { + Thread.sleep(3000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + MainActivity activity = mActivityRule.getActivity(); + getInstrumentation().runOnMainSync(new Runnable() { + @Override + public void run() { + activity.finish(); + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }); + + mActivityRule.getActivity(); + getInstrumentation().waitForIdleSync(); + onView(withId(R.id.recording_status_text)).check(matches(withText(containsString(recording)))); + onView(withId(R.id.tvChronometer)).check(matches(withText(not(containsString("00:00"))))); + + onView(withId(R.id.btnRecord)).perform(click()); + onView(withId(R.id.recording_status_text)).check(matches(withText(containsString(prompt)))); + onView(withId(R.id.tvChronometer)).check(matches(withText(containsString("00:00")))); + } } From c123b21ee86672a50334832cba0e10f07ba6c4a2 Mon Sep 17 00:00:00 2001 From: iClaude Date: Fri, 20 Oct 2017 14:39:12 +0200 Subject: [PATCH 71/96] functional test: add a scheduled recording with correct data --- .../MainActivityEspressoTest.java | 92 ++++++++++++++++++- .../AddScheduledRecordingActivity.java | 16 ++++ .../activities/MainActivity.java | 1 + .../activity_add_scheduled_recording.xml | 2 +- app/src/main/res/values-it/strings.xml | 27 ++++++ others/notes | 1 - 6 files changed, 135 insertions(+), 4 deletions(-) diff --git a/app/src/androidTest/java/com/danielkim/soundrecorder/MainActivityEspressoTest.java b/app/src/androidTest/java/com/danielkim/soundrecorder/MainActivityEspressoTest.java index 504c7249..113b3936 100644 --- a/app/src/androidTest/java/com/danielkim/soundrecorder/MainActivityEspressoTest.java +++ b/app/src/androidTest/java/com/danielkim/soundrecorder/MainActivityEspressoTest.java @@ -4,24 +4,41 @@ package com.danielkim.soundrecorder; +import android.app.Activity; +import android.content.Intent; +import android.support.test.InstrumentationRegistry; +import android.support.test.espresso.ViewInteraction; import android.support.test.filters.LargeTest; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; +import android.support.test.runner.lifecycle.ActivityLifecycleMonitorRegistry; +import com.danielkim.soundrecorder.activities.AddScheduledRecordingActivity; import com.danielkim.soundrecorder.activities.MainActivity; +import org.hamcrest.Matchers; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.Calendar; +import java.util.Collection; +import java.util.GregorianCalendar; + import static android.support.test.InstrumentationRegistry.getInstrumentation; import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.action.ViewActions.longClick; +import static android.support.test.espresso.action.ViewActions.scrollTo; import static android.support.test.espresso.action.ViewActions.swipeLeft; +import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist; import static android.support.test.espresso.assertion.ViewAssertions.matches; import static android.support.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition; +import static android.support.test.espresso.contrib.RecyclerViewActions.scrollToPosition; +import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; import static android.support.test.espresso.matcher.ViewMatchers.withId; import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static android.support.test.runner.lifecycle.Stage.RESUMED; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.not; @@ -110,6 +127,11 @@ public void run() { onView(withId(R.id.tvChronometer)).check(matches(withText(containsString("00:00")))); } + /* + Checks that: + - when I destroy the Activity while recording the recording continues + - when I start the Activity again the UI shows the ongoing recording correctly + */ @Test public void destroyActivityWhileRecording() { String recording = mActivityRule.getActivity().getResources().getString(R.string.record_in_progress); @@ -135,8 +157,13 @@ public void run() { } }); - mActivityRule.getActivity(); - getInstrumentation().waitForIdleSync(); + getInstrumentation().startActivitySync(new Intent(InstrumentationRegistry.getTargetContext(), MainActivity.class)); + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + onView(withId(R.id.recording_status_text)).check(matches(withText(containsString(recording)))); onView(withId(R.id.tvChronometer)).check(matches(withText(not(containsString("00:00"))))); @@ -144,4 +171,65 @@ public void run() { onView(withId(R.id.recording_status_text)).check(matches(withText(containsString(prompt)))); onView(withId(R.id.tvChronometer)).check(matches(withText(containsString("00:00")))); } + + /* + Add a new scheduled recording with correct data. + Check that the recording is added to the list. + Delete the recording. + Check that the recording is no longer in the list. + */ + @Test + public void addScheduledRecordingCorrect() { + String save = mActivityRule.getActivity().getResources().getString(R.string.action_save); + + onView(withId(R.id.pager)).perform(swipeLeft()); + onView(withId(R.id.pager)).perform(swipeLeft()); // go to ScheduledRecordingsFragment + onView(withId(R.id.fab_add)).perform(click()); // click on add new scheduled recording button + + // Set date and time for a scheduled recording in AddScheduledRecordingActivity. + GregorianCalendar tomorrow = new GregorianCalendar(); + int year = tomorrow.get(Calendar.YEAR); + int month = tomorrow.get(Calendar.MONTH); + int day = tomorrow.get(Calendar.DAY_OF_MONTH); + AddScheduledRecordingActivity activity = (AddScheduledRecordingActivity) getActivityInstance(); + activity.setDatesAndTimesForTesting(year, month, day, 23, 0, year, month, day, 23, 5); + + // Click on action save. + ViewInteraction actionMenuItemView = onView( + Matchers.allOf(withId(R.id.action_save), withText(save), isDisplayed())); + actionMenuItemView.perform(click()); + + // Check that the new scheduled recording is added to the list. + String scheduledRecording = mActivityRule.getActivity().getResources().getString(R.string.frag_sched_scheduled_recording); + onView(withId(R.id.rvRecordings)).perform(scrollToPosition(0)); + onView(withText(scheduledRecording)).check(matches(isDisplayed())); + onView(withText("23:00")).check(matches(isDisplayed())); + onView(withText("23:05")).check(matches(isDisplayed())); + + // Delete the scheduled recording. + onView(withText("23:00")).perform(longClick()); + ViewInteraction appCompatButton3 = onView( + Matchers.allOf(withId(android.R.id.button1), withText("OK"))); + appCompatButton3.perform(scrollTo(), click()); + + // Check that the scheduled recording is no longer in the list. + onView(withText("23:00")).check(doesNotExist()); + } + + public Activity getActivityInstance() { + final Activity[] activity = new Activity[1]; + InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { + public void run() { + Activity currentActivity = null; + Collection resumedActivities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(RESUMED); + if (resumedActivities.iterator().hasNext()) { + currentActivity = (Activity) resumedActivities.iterator().next(); + activity[0] = currentActivity; + } + } + }); + + return activity[0]; + } + } diff --git a/app/src/main/java/com/danielkim/soundrecorder/activities/AddScheduledRecordingActivity.java b/app/src/main/java/com/danielkim/soundrecorder/activities/AddScheduledRecordingActivity.java index 28091e44..d3a82ce2 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/activities/AddScheduledRecordingActivity.java +++ b/app/src/main/java/com/danielkim/soundrecorder/activities/AddScheduledRecordingActivity.java @@ -11,6 +11,7 @@ import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; import android.support.v4.content.ContextCompat; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; @@ -314,4 +315,19 @@ protected void onPostExecute(Integer result) { } } } + + @VisibleForTesting + public void setDatesAndTimesForTesting(int yearStart, int monthStart, int dayStart, int hourStart, int minuteStart, int yearEnd, int monthEnd, int dayEnd, int hourEnd, int minuteEnd) { + this.yearStart = yearStart; + this.yearEnd = yearEnd; + this.monthStart = monthStart; + this.monthEnd = monthEnd; + this.dayStart = dayStart; + this.dayEnd = dayEnd; + this.hourStart = hourStart; + this.hourEnd = hourEnd; + this.minuteStart = minuteStart; + this.minuteEnd = minuteEnd; + statusCode = StatusCodes.NO_ERROR; + } } diff --git a/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java b/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java index 2c6468d1..f7ba3c33 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java +++ b/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java @@ -35,6 +35,7 @@ public class MainActivity extends AppCompatActivity implements RecordFragment.Se @Override protected void onCreate(Bundle savedInstanceState) { + Log.d(TAG, "onCreate: called"); super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); diff --git a/app/src/main/res/layout/activity_add_scheduled_recording.xml b/app/src/main/res/layout/activity_add_scheduled_recording.xml index 8e9830db..35de5db2 100644 --- a/app/src/main/res/layout/activity_add_scheduled_recording.xml +++ b/app/src/main/res/layout/activity_add_scheduled_recording.xml @@ -10,7 +10,7 @@ Salva Registra Registrazioni salvate + Registrazioni pianificate + Inizio registrazione @@ -33,9 +35,11 @@ Sei sicuro di voler cancellare questo file? Sei sicuro di voler cancellare questo elemento? Licenze open source + Condividi File Rinomina file Opzioni + Condividi File Rinomina file Elimina file Modifica @@ -52,4 +56,27 @@ Premi il bottone per iniziare a registrare Registrazione in corso + Invia a + + + Registrazioni pianificate + registrazione pianificata + nessuna registrazione + Pianifica registrazione + + Nuovo messaggio: %1$s + + + You said %1$s and lorem ipsum + dolor sit amet, consectetur adipiscing elit. Etiam non enim magna. Morbi dictum, velit vel + semper venenatis, magna odio volutpat velit, at ullamcorper nulla lacus sed turpis. + Pellentesque vitae metus elit, nec tincidunt tellus. Integer sed nisl sem, ullamcorper + ornare lacus. Duis ac mauris sed massa congue volutpat. Donec sed erat sit amet turpis + viverra rhoncus sit amet nec magna. Donec lacinia ligula at libero volutpat volutpat nec nec + tortor. + + + Condividi + Rispondi + diff --git a/others/notes b/others/notes index 66dada95..11c31c49 100644 --- a/others/notes +++ b/others/notes @@ -1,4 +1,3 @@ da sistemare: -- rivedere test - creare nuovi functional test - eliminare tutti i log \ No newline at end of file From bdccc3c5ae6eae2f453b3c539ca228cc8826fa03 Mon Sep 17 00:00:00 2001 From: iClaude Date: Fri, 20 Oct 2017 14:55:26 +0200 Subject: [PATCH 72/96] functional test: add a scheduled recording with too short duration --- .../MainActivityEspressoTest.java | 52 +++++++++++++++++-- .../AddScheduledRecordingActivity.java | 4 +- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/app/src/androidTest/java/com/danielkim/soundrecorder/MainActivityEspressoTest.java b/app/src/androidTest/java/com/danielkim/soundrecorder/MainActivityEspressoTest.java index 113b3936..beb0b19e 100644 --- a/app/src/androidTest/java/com/danielkim/soundrecorder/MainActivityEspressoTest.java +++ b/app/src/androidTest/java/com/danielkim/soundrecorder/MainActivityEspressoTest.java @@ -187,10 +187,10 @@ public void addScheduledRecordingCorrect() { onView(withId(R.id.fab_add)).perform(click()); // click on add new scheduled recording button // Set date and time for a scheduled recording in AddScheduledRecordingActivity. - GregorianCalendar tomorrow = new GregorianCalendar(); - int year = tomorrow.get(Calendar.YEAR); - int month = tomorrow.get(Calendar.MONTH); - int day = tomorrow.get(Calendar.DAY_OF_MONTH); + GregorianCalendar today = new GregorianCalendar(); + int year = today.get(Calendar.YEAR); + int month = today.get(Calendar.MONTH); + int day = today.get(Calendar.DAY_OF_MONTH); AddScheduledRecordingActivity activity = (AddScheduledRecordingActivity) getActivityInstance(); activity.setDatesAndTimesForTesting(year, month, day, 23, 0, year, month, day, 23, 5); @@ -216,6 +216,50 @@ public void addScheduledRecordingCorrect() { onView(withText("23:00")).check(doesNotExist()); } + /* + Add a new scheduled recording with a duration of 3 minutes. + Check that the recording is added to the list with a duration of 5 minutes. + Delete the recording. + Check that the recording is no longer in the list. + */ + @Test + public void addScheduledRecording3Minutes() { + String save = mActivityRule.getActivity().getResources().getString(R.string.action_save); + + onView(withId(R.id.pager)).perform(swipeLeft()); + onView(withId(R.id.pager)).perform(swipeLeft()); // go to ScheduledRecordingsFragment + onView(withId(R.id.fab_add)).perform(click()); // click on add new scheduled recording button + + // Set date and time for a scheduled recording in AddScheduledRecordingActivity. + GregorianCalendar today = new GregorianCalendar(); + int year = today.get(Calendar.YEAR); + int month = today.get(Calendar.MONTH); + int day = today.get(Calendar.DAY_OF_MONTH); + AddScheduledRecordingActivity activity = (AddScheduledRecordingActivity) getActivityInstance(); + activity.setDatesAndTimesForTesting(year, month, day, 23, 0, year, month, day, 23, 3); + + // Click on action save. + ViewInteraction actionMenuItemView = onView( + Matchers.allOf(withId(R.id.action_save), withText(save), isDisplayed())); + actionMenuItemView.perform(click()); + + // Check that the new scheduled recording is added to the list. + String scheduledRecording = mActivityRule.getActivity().getResources().getString(R.string.frag_sched_scheduled_recording); + onView(withId(R.id.rvRecordings)).perform(scrollToPosition(0)); + onView(withText(scheduledRecording)).check(matches(isDisplayed())); + onView(withText("23:00")).check(matches(isDisplayed())); + onView(withText("23:05")).check(matches(isDisplayed())); + + // Delete the scheduled recording. + onView(withText("23:00")).perform(longClick()); + ViewInteraction appCompatButton3 = onView( + Matchers.allOf(withId(android.R.id.button1), withText("OK"))); + appCompatButton3.perform(scrollTo(), click()); + + // Check that the scheduled recording is no longer in the list. + onView(withText("23:00")).check(doesNotExist()); + } + public Activity getActivityInstance() { final Activity[] activity = new Activity[1]; InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { diff --git a/app/src/main/java/com/danielkim/soundrecorder/activities/AddScheduledRecordingActivity.java b/app/src/main/java/com/danielkim/soundrecorder/activities/AddScheduledRecordingActivity.java index d3a82ce2..bda7983a 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/activities/AddScheduledRecordingActivity.java +++ b/app/src/main/java/com/danielkim/soundrecorder/activities/AddScheduledRecordingActivity.java @@ -316,7 +316,7 @@ protected void onPostExecute(Integer result) { } } - @VisibleForTesting + @VisibleForTesting(otherwise = VisibleForTesting.NONE) public void setDatesAndTimesForTesting(int yearStart, int monthStart, int dayStart, int hourStart, int minuteStart, int yearEnd, int monthEnd, int dayEnd, int hourEnd, int minuteEnd) { this.yearStart = yearStart; this.yearEnd = yearEnd; @@ -328,6 +328,6 @@ public void setDatesAndTimesForTesting(int yearStart, int monthStart, int daySta this.hourEnd = hourEnd; this.minuteStart = minuteStart; this.minuteEnd = minuteEnd; - statusCode = StatusCodes.NO_ERROR; + statusCode = getTimeErrorCode(); } } From 365e1987efa6ffb72219dd0142ddb70e1e958d0a Mon Sep 17 00:00:00 2001 From: iClaude Date: Fri, 20 Oct 2017 18:46:08 +0200 Subject: [PATCH 73/96] functional tests: delete files for recording tests and all tests reorganized --- ...oTest.java => EspressoRecordFragment.java} | 172 +++++--------- .../EspressoScheduledRecordingsFragment.java | 213 ++++++++++++++++++ .../activities/MainActivityTest.java | 109 +++++++++ 3 files changed, 382 insertions(+), 112 deletions(-) rename app/src/androidTest/java/com/danielkim/soundrecorder/{MainActivityEspressoTest.java => EspressoRecordFragment.java} (53%) create mode 100644 app/src/androidTest/java/com/danielkim/soundrecorder/EspressoScheduledRecordingsFragment.java create mode 100644 app/src/androidTest/java/com/danielkim/soundrecorder/activities/MainActivityTest.java diff --git a/app/src/androidTest/java/com/danielkim/soundrecorder/MainActivityEspressoTest.java b/app/src/androidTest/java/com/danielkim/soundrecorder/EspressoRecordFragment.java similarity index 53% rename from app/src/androidTest/java/com/danielkim/soundrecorder/MainActivityEspressoTest.java rename to app/src/androidTest/java/com/danielkim/soundrecorder/EspressoRecordFragment.java index beb0b19e..5a22589d 100644 --- a/app/src/androidTest/java/com/danielkim/soundrecorder/MainActivityEspressoTest.java +++ b/app/src/androidTest/java/com/danielkim/soundrecorder/EspressoRecordFragment.java @@ -4,41 +4,35 @@ package com.danielkim.soundrecorder; -import android.app.Activity; import android.content.Intent; import android.support.test.InstrumentationRegistry; -import android.support.test.espresso.ViewInteraction; import android.support.test.filters.LargeTest; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; -import android.support.test.runner.lifecycle.ActivityLifecycleMonitorRegistry; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; -import com.danielkim.soundrecorder.activities.AddScheduledRecordingActivity; import com.danielkim.soundrecorder.activities.MainActivity; -import org.hamcrest.Matchers; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.Calendar; -import java.util.Collection; -import java.util.GregorianCalendar; - import static android.support.test.InstrumentationRegistry.getInstrumentation; import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.Espresso.pressBack; import static android.support.test.espresso.action.ViewActions.click; import static android.support.test.espresso.action.ViewActions.longClick; -import static android.support.test.espresso.action.ViewActions.scrollTo; import static android.support.test.espresso.action.ViewActions.swipeLeft; import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist; import static android.support.test.espresso.assertion.ViewAssertions.matches; import static android.support.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition; -import static android.support.test.espresso.contrib.RecyclerViewActions.scrollToPosition; -import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; import static android.support.test.espresso.matcher.ViewMatchers.withId; import static android.support.test.espresso.matcher.ViewMatchers.withText; -import static android.support.test.runner.lifecycle.Stage.RESUMED; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.not; @@ -48,7 +42,7 @@ @RunWith(AndroidJUnit4.class) @LargeTest -public class MainActivityEspressoTest { +public class EspressoRecordFragment { @Rule public ActivityTestRule mActivityRule = new ActivityTestRule<>(MainActivity.class); @@ -79,10 +73,22 @@ public void startAndStopRecording() { onView(withId(R.id.tvChronometer)).check(matches(withText(containsString("00:00")))); // Check that the recording is added to FileViewerFragment. - String defaultFileName = mActivityRule.getActivity().getResources().getString(R.string.default_file_name); + String myRecordings = mActivityRule.getActivity().getResources().getString(R.string.default_file_name); onView(withId(R.id.pager)).perform(swipeLeft()); onView(withId(R.id.recyclerView)).perform(actionOnItemAtPosition(0, click())); - onView(withId(R.id.file_name_text_view)).check(matches(withText(containsString(defaultFileName)))); + onView(withId(R.id.file_name_text_view)).check(matches(withText(containsString(myRecordings)))); + pressBack(); + + // Delete the recording. + String deleteFile = mActivityRule.getActivity().getResources().getString(R.string.dialog_file_delete); + String yes = mActivityRule.getActivity().getResources().getString(R.string.dialog_action_yes); + + onView(withText(containsString(myRecordings))).perform(longClick()); + onView(withText(containsString(deleteFile))).perform(click()); + onView(withText(containsString(yes))).perform(click()); + + // Check that the recording is no longer in the list. + onView(withText(containsString(myRecordings))).check(doesNotExist()); } /* @@ -125,6 +131,20 @@ public void run() { onView(withId(R.id.btnRecord)).perform(click()); onView(withId(R.id.recording_status_text)).check(matches(withText(containsString(prompt)))); onView(withId(R.id.tvChronometer)).check(matches(withText(containsString("00:00")))); + + // Delete the recording. + String myRecordings = mActivityRule.getActivity().getResources().getString(R.string.default_file_name); + String deleteFile = mActivityRule.getActivity().getResources().getString(R.string.dialog_file_delete); + String yes = mActivityRule.getActivity().getResources().getString(R.string.dialog_action_yes); + + onView(withId(R.id.pager)).perform(swipeLeft()); + onView(withText(containsString(myRecordings))).perform(longClick()); + onView(withText(containsString(deleteFile))).perform(click()); + onView(withText(containsString(yes))).perform(click()); + + // Check that the recording is no longer in the list. + onView(withText(containsString(myRecordings))).check(doesNotExist()); + } /* @@ -170,110 +190,38 @@ public void run() { onView(withId(R.id.btnRecord)).perform(click()); onView(withId(R.id.recording_status_text)).check(matches(withText(containsString(prompt)))); onView(withId(R.id.tvChronometer)).check(matches(withText(containsString("00:00")))); - } - /* - Add a new scheduled recording with correct data. - Check that the recording is added to the list. - Delete the recording. - Check that the recording is no longer in the list. - */ - @Test - public void addScheduledRecordingCorrect() { - String save = mActivityRule.getActivity().getResources().getString(R.string.action_save); + // Delete the recording. + String myRecordings = mActivityRule.getActivity().getResources().getString(R.string.default_file_name); + String deleteFile = mActivityRule.getActivity().getResources().getString(R.string.dialog_file_delete); + String yes = mActivityRule.getActivity().getResources().getString(R.string.dialog_action_yes); onView(withId(R.id.pager)).perform(swipeLeft()); - onView(withId(R.id.pager)).perform(swipeLeft()); // go to ScheduledRecordingsFragment - onView(withId(R.id.fab_add)).perform(click()); // click on add new scheduled recording button - - // Set date and time for a scheduled recording in AddScheduledRecordingActivity. - GregorianCalendar today = new GregorianCalendar(); - int year = today.get(Calendar.YEAR); - int month = today.get(Calendar.MONTH); - int day = today.get(Calendar.DAY_OF_MONTH); - AddScheduledRecordingActivity activity = (AddScheduledRecordingActivity) getActivityInstance(); - activity.setDatesAndTimesForTesting(year, month, day, 23, 0, year, month, day, 23, 5); - - // Click on action save. - ViewInteraction actionMenuItemView = onView( - Matchers.allOf(withId(R.id.action_save), withText(save), isDisplayed())); - actionMenuItemView.perform(click()); - - // Check that the new scheduled recording is added to the list. - String scheduledRecording = mActivityRule.getActivity().getResources().getString(R.string.frag_sched_scheduled_recording); - onView(withId(R.id.rvRecordings)).perform(scrollToPosition(0)); - onView(withText(scheduledRecording)).check(matches(isDisplayed())); - onView(withText("23:00")).check(matches(isDisplayed())); - onView(withText("23:05")).check(matches(isDisplayed())); - - // Delete the scheduled recording. - onView(withText("23:00")).perform(longClick()); - ViewInteraction appCompatButton3 = onView( - Matchers.allOf(withId(android.R.id.button1), withText("OK"))); - appCompatButton3.perform(scrollTo(), click()); - - // Check that the scheduled recording is no longer in the list. - onView(withText("23:00")).check(doesNotExist()); - } + onView(withText(containsString(myRecordings))).perform(longClick()); + onView(withText(containsString(deleteFile))).perform(click()); + onView(withText(containsString(yes))).perform(click()); - /* - Add a new scheduled recording with a duration of 3 minutes. - Check that the recording is added to the list with a duration of 5 minutes. - Delete the recording. - Check that the recording is no longer in the list. - */ - @Test - public void addScheduledRecording3Minutes() { - String save = mActivityRule.getActivity().getResources().getString(R.string.action_save); - - onView(withId(R.id.pager)).perform(swipeLeft()); - onView(withId(R.id.pager)).perform(swipeLeft()); // go to ScheduledRecordingsFragment - onView(withId(R.id.fab_add)).perform(click()); // click on add new scheduled recording button - - // Set date and time for a scheduled recording in AddScheduledRecordingActivity. - GregorianCalendar today = new GregorianCalendar(); - int year = today.get(Calendar.YEAR); - int month = today.get(Calendar.MONTH); - int day = today.get(Calendar.DAY_OF_MONTH); - AddScheduledRecordingActivity activity = (AddScheduledRecordingActivity) getActivityInstance(); - activity.setDatesAndTimesForTesting(year, month, day, 23, 0, year, month, day, 23, 3); - - // Click on action save. - ViewInteraction actionMenuItemView = onView( - Matchers.allOf(withId(R.id.action_save), withText(save), isDisplayed())); - actionMenuItemView.perform(click()); - - // Check that the new scheduled recording is added to the list. - String scheduledRecording = mActivityRule.getActivity().getResources().getString(R.string.frag_sched_scheduled_recording); - onView(withId(R.id.rvRecordings)).perform(scrollToPosition(0)); - onView(withText(scheduledRecording)).check(matches(isDisplayed())); - onView(withText("23:00")).check(matches(isDisplayed())); - onView(withText("23:05")).check(matches(isDisplayed())); - - // Delete the scheduled recording. - onView(withText("23:00")).perform(longClick()); - ViewInteraction appCompatButton3 = onView( - Matchers.allOf(withId(android.R.id.button1), withText("OK"))); - appCompatButton3.perform(scrollTo(), click()); - - // Check that the scheduled recording is no longer in the list. - onView(withText("23:00")).check(doesNotExist()); + // Check that the recording is no longer in the list. + onView(withText(containsString(myRecordings))).check(doesNotExist()); } - public Activity getActivityInstance() { - final Activity[] activity = new Activity[1]; - InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { - public void run() { - Activity currentActivity = null; - Collection resumedActivities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(RESUMED); - if (resumedActivities.iterator().hasNext()) { - currentActivity = (Activity) resumedActivities.iterator().next(); - activity[0] = currentActivity; - } + private static Matcher childAtPosition( + final Matcher parentMatcher, final int position) { + + return new TypeSafeMatcher() { + @Override + public void describeTo(Description description) { + description.appendText("Child at position " + position + " in parent "); + parentMatcher.describeTo(description); } - }); - return activity[0]; + @Override + public boolean matchesSafely(View view) { + ViewParent parent = view.getParent(); + return parent instanceof ViewGroup && parentMatcher.matches(parent) + && view.equals(((ViewGroup) parent).getChildAt(position)); + } + }; } } diff --git a/app/src/androidTest/java/com/danielkim/soundrecorder/EspressoScheduledRecordingsFragment.java b/app/src/androidTest/java/com/danielkim/soundrecorder/EspressoScheduledRecordingsFragment.java new file mode 100644 index 00000000..33b779e4 --- /dev/null +++ b/app/src/androidTest/java/com/danielkim/soundrecorder/EspressoScheduledRecordingsFragment.java @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2017. This code was written by iClaude. All rights reserved. + */ + +package com.danielkim.soundrecorder; + +import android.app.Activity; +import android.support.test.InstrumentationRegistry; +import android.support.test.espresso.ViewInteraction; +import android.support.test.rule.ActivityTestRule; +import android.support.test.runner.lifecycle.ActivityLifecycleMonitorRegistry; + +import com.danielkim.soundrecorder.activities.AddScheduledRecordingActivity; +import com.danielkim.soundrecorder.activities.MainActivity; + +import org.hamcrest.Matchers; +import org.junit.Rule; +import org.junit.Test; + +import java.util.Calendar; +import java.util.Collection; +import java.util.GregorianCalendar; + +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.action.ViewActions.longClick; +import static android.support.test.espresso.action.ViewActions.scrollTo; +import static android.support.test.espresso.action.ViewActions.swipeLeft; +import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist; +import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.contrib.RecyclerViewActions.scrollToPosition; +import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; +import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static android.support.test.runner.lifecycle.Stage.RESUMED; +import static junit.framework.Assert.assertTrue; + +/** + * Created by iClaude on 20/10/2017. + */ + +public class EspressoScheduledRecordingsFragment { + + @Rule + public ActivityTestRule mActivityRule = new ActivityTestRule<>(MainActivity.class); + + /* + Add a new scheduled recording with correct data. + Check that the recording is added to the list. + Delete the recording. + Check that the recording is no longer in the list. + */ + @Test + public void addScheduledRecordingCorrect() { + String save = mActivityRule.getActivity().getResources().getString(R.string.action_save); + + onView(withId(R.id.pager)).perform(swipeLeft()); + onView(withId(R.id.pager)).perform(swipeLeft()); // go to ScheduledRecordingsFragment + onView(withId(R.id.fab_add)).perform(click()); // click on add new scheduled recording button + + // Set date and time for a scheduled recording in AddScheduledRecordingActivity. + GregorianCalendar today = new GregorianCalendar(); + int year = today.get(Calendar.YEAR); + int month = today.get(Calendar.MONTH); + int day = today.get(Calendar.DAY_OF_MONTH); + AddScheduledRecordingActivity activity = (AddScheduledRecordingActivity) getActivityInstance(); + activity.setDatesAndTimesForTesting(year, month, day, 23, 0, year, month, day, 23, 5); + + // Click on action save. + ViewInteraction actionMenuItemView = onView( + Matchers.allOf(withId(R.id.action_save), withText(save), isDisplayed())); + actionMenuItemView.perform(click()); + + // Check that the new scheduled recording is added to the list. + String scheduledRecording = mActivityRule.getActivity().getResources().getString(R.string.frag_sched_scheduled_recording); + onView(withId(R.id.rvRecordings)).perform(scrollToPosition(0)); + onView(withText(scheduledRecording)).check(matches(isDisplayed())); + onView(withText("23:00")).check(matches(isDisplayed())); + onView(withText("23:05")).check(matches(isDisplayed())); + + // Delete the scheduled recording. + onView(withText("23:00")).perform(longClick()); + ViewInteraction appCompatButton3 = onView( + Matchers.allOf(withId(android.R.id.button1), withText("OK"))); + appCompatButton3.perform(scrollTo(), click()); + + // Check that the scheduled recording is no longer in the list. + onView(withText("23:00")).check(doesNotExist()); + } + + /* + Add a new scheduled recording with a duration of 3 minutes. + Check that the recording is added to the list with a duration of 5 minutes. + Delete the recording. + Check that the recording is no longer in the list. + */ + @Test + public void addScheduledRecording3Minutes() { + String save = mActivityRule.getActivity().getResources().getString(R.string.action_save); + + onView(withId(R.id.pager)).perform(swipeLeft()); + onView(withId(R.id.pager)).perform(swipeLeft()); // go to ScheduledRecordingsFragment + onView(withId(R.id.fab_add)).perform(click()); // click on add new scheduled recording button + + // Set date and time for a scheduled recording in AddScheduledRecordingActivity. + GregorianCalendar today = new GregorianCalendar(); + int year = today.get(Calendar.YEAR); + int month = today.get(Calendar.MONTH); + int day = today.get(Calendar.DAY_OF_MONTH); + AddScheduledRecordingActivity activity = (AddScheduledRecordingActivity) getActivityInstance(); + activity.setDatesAndTimesForTesting(year, month, day, 23, 0, year, month, day, 23, 3); + + // Click on action save. + ViewInteraction actionMenuItemView = onView( + Matchers.allOf(withId(R.id.action_save), withText(save), isDisplayed())); + actionMenuItemView.perform(click()); + + // Check that the new scheduled recording is added to the list. + String scheduledRecording = mActivityRule.getActivity().getResources().getString(R.string.frag_sched_scheduled_recording); + onView(withId(R.id.rvRecordings)).perform(scrollToPosition(0)); + onView(withText(scheduledRecording)).check(matches(isDisplayed())); + onView(withText("23:00")).check(matches(isDisplayed())); + onView(withText("23:05")).check(matches(isDisplayed())); + + // Delete the scheduled recording. + onView(withText("23:00")).perform(longClick()); + ViewInteraction appCompatButton3 = onView( + Matchers.allOf(withId(android.R.id.button1), withText("OK"))); + appCompatButton3.perform(scrollTo(), click()); + + // Check that the scheduled recording is no longer in the list. + onView(withText("23:00")).check(doesNotExist()); + } + + /* + Try to add a scheduled recording in the past. + It should not be added (it should stay in the same Activity). + */ + @Test + public void addScheduledRecordingPast() { + String save = mActivityRule.getActivity().getResources().getString(R.string.action_save); + + onView(withId(R.id.pager)).perform(swipeLeft()); + onView(withId(R.id.pager)).perform(swipeLeft()); // go to ScheduledRecordingsFragment + onView(withId(R.id.fab_add)).perform(click()); // click on add new scheduled recording button + + // Set date and time for a scheduled recording in AddScheduledRecordingActivity. + GregorianCalendar today = new GregorianCalendar(); + today.add(Calendar.DAY_OF_MONTH, -2); + int year = today.get(Calendar.YEAR); + int month = today.get(Calendar.MONTH); + int day = today.get(Calendar.DAY_OF_MONTH); + AddScheduledRecordingActivity activity = (AddScheduledRecordingActivity) getActivityInstance(); + activity.setDatesAndTimesForTesting(year, month, day, 23, 0, year, month, day, 23, 10); + + // Click on action save. + ViewInteraction actionMenuItemView = onView( + Matchers.allOf(withId(R.id.action_save), withText(save), isDisplayed())); + actionMenuItemView.perform(click()); + + // Check that we are still in the same Activity (scheduled recording not added). + Activity currentActivity = getActivityInstance(); + boolean b = currentActivity instanceof AddScheduledRecordingActivity; + assertTrue("We should be in AddScheduledRecordingActivity", b); + } + + /* + Try to add a scheduled recording with end time before start time. + It should not be added (it should stay in the same Activity). + */ + @Test + public void addScheduledRecordingTimesMismatch() { + String save = mActivityRule.getActivity().getResources().getString(R.string.action_save); + + onView(withId(R.id.pager)).perform(swipeLeft()); + onView(withId(R.id.pager)).perform(swipeLeft()); // go to ScheduledRecordingsFragment + onView(withId(R.id.fab_add)).perform(click()); // click on add new scheduled recording button + + // Set date and time for a scheduled recording in AddScheduledRecordingActivity. + GregorianCalendar today = new GregorianCalendar(); + int year = today.get(Calendar.YEAR); + int month = today.get(Calendar.MONTH); + int day = today.get(Calendar.DAY_OF_MONTH); + AddScheduledRecordingActivity activity = (AddScheduledRecordingActivity) getActivityInstance(); + activity.setDatesAndTimesForTesting(year, month, day, 23, 10, year, month, day, 23, 0); + + // Click on action save. + ViewInteraction actionMenuItemView = onView( + Matchers.allOf(withId(R.id.action_save), withText(save), isDisplayed())); + actionMenuItemView.perform(click()); + + // Check that we are still in the same Activity (scheduled recording not added). + Activity currentActivity = getActivityInstance(); + boolean b = currentActivity instanceof AddScheduledRecordingActivity; + assertTrue("We should be in AddScheduledRecordingActivity", b); + } + + public Activity getActivityInstance() { + final Activity[] activity = new Activity[1]; + InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { + public void run() { + Activity currentActivity = null; + Collection resumedActivities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(RESUMED); + if (resumedActivities.iterator().hasNext()) { + currentActivity = (Activity) resumedActivities.iterator().next(); + activity[0] = currentActivity; + } + } + }); + + return activity[0]; + } +} diff --git a/app/src/androidTest/java/com/danielkim/soundrecorder/activities/MainActivityTest.java b/app/src/androidTest/java/com/danielkim/soundrecorder/activities/MainActivityTest.java new file mode 100644 index 00000000..24750cb9 --- /dev/null +++ b/app/src/androidTest/java/com/danielkim/soundrecorder/activities/MainActivityTest.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2017. This code was written by iClaude. All rights reserved. + */ + +package com.danielkim.soundrecorder.activities; + + +import android.support.test.espresso.ViewInteraction; +import android.support.test.rule.ActivityTestRule; +import android.support.test.runner.AndroidJUnit4; +import android.test.suitebuilder.annotation.LargeTest; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; + +import com.danielkim.soundrecorder.R; + +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.action.ViewActions.longClick; +import static android.support.test.espresso.action.ViewActions.scrollTo; +import static android.support.test.espresso.action.ViewActions.swipeLeft; +import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; +import static android.support.test.espresso.matcher.ViewMatchers.withClassName; +import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static android.support.test.espresso.matcher.ViewMatchers.withParent; +import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.is; + +@LargeTest +@RunWith(AndroidJUnit4.class) +public class MainActivityTest { + + @Rule + public ActivityTestRule mActivityTestRule = new ActivityTestRule<>(MainActivity.class); + + @Test + public void mainActivityTest() { + ViewInteraction floatingActionButton = onView( + allOf(withId(R.id.btnRecord), + withParent(allOf(withId(R.id.fragment_record), + withParent(withId(R.id.pager)))), + isDisplayed())); + floatingActionButton.perform(click()); + + ViewInteraction floatingActionButton2 = onView( + allOf(withId(R.id.btnRecord), + withParent(allOf(withId(R.id.fragment_record), + withParent(withId(R.id.pager)))), + isDisplayed())); + floatingActionButton2.perform(click()); + + ViewInteraction viewPager = onView( + allOf(withId(R.id.pager), + withParent(allOf(withId(R.id.main_activity), + withParent(withId(android.R.id.content)))), + isDisplayed())); + viewPager.perform(swipeLeft()); + + ViewInteraction cardView = onView( + allOf(withId(R.id.card_view), isDisplayed())); + cardView.perform(longClick()); + + ViewInteraction cardView2 = onView( + allOf(withId(R.id.card_view), isDisplayed())); + cardView2.perform(click()); + + ViewInteraction appCompatTextView = onView( + allOf(withId(android.R.id.text1), withText("Elimina file"), + childAtPosition( + allOf(withClassName(is("com.android.internal.app.AlertController$RecycleListView")), + withParent(withClassName(is("android.widget.FrameLayout")))), + 2), + isDisplayed())); + appCompatTextView.perform(click()); + + ViewInteraction appCompatButton = onView( + allOf(withId(android.R.id.button1), withText("Sì"))); + appCompatButton.perform(scrollTo(), click()); + + } + + private static Matcher childAtPosition( + final Matcher parentMatcher, final int position) { + + return new TypeSafeMatcher() { + @Override + public void describeTo(Description description) { + description.appendText("Child at position " + position + " in parent "); + parentMatcher.describeTo(description); + } + + @Override + public boolean matchesSafely(View view) { + ViewParent parent = view.getParent(); + return parent instanceof ViewGroup && parentMatcher.matches(parent) + && view.equals(((ViewGroup) parent).getChildAt(position)); + } + }; + } +} From fc14926532f33ad0e689ba4d0e6dc70235a414ba Mon Sep 17 00:00:00 2001 From: iClaude Date: Sun, 22 Oct 2017 17:01:58 +0200 Subject: [PATCH 74/96] bug fixes --- .../danielkim/soundrecorder/EspressoRecordFragment.java | 4 +++- .../EspressoScheduledRecordingsFragment.java | 9 ++------- .../danielkim/soundrecorder/activities/MainActivity.java | 1 + .../soundrecorder/fragments/RecordFragment.java | 4 +++- gradle.properties | 1 + 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/app/src/androidTest/java/com/danielkim/soundrecorder/EspressoRecordFragment.java b/app/src/androidTest/java/com/danielkim/soundrecorder/EspressoRecordFragment.java index 5a22589d..879c2c89 100644 --- a/app/src/androidTest/java/com/danielkim/soundrecorder/EspressoRecordFragment.java +++ b/app/src/androidTest/java/com/danielkim/soundrecorder/EspressoRecordFragment.java @@ -177,7 +177,9 @@ public void run() { } }); - getInstrumentation().startActivitySync(new Intent(InstrumentationRegistry.getTargetContext(), MainActivity.class)); + Intent intent = new Intent(InstrumentationRegistry.getTargetContext(), MainActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + getInstrumentation().startActivitySync(intent); try { Thread.sleep(2000); } catch (InterruptedException e) { diff --git a/app/src/androidTest/java/com/danielkim/soundrecorder/EspressoScheduledRecordingsFragment.java b/app/src/androidTest/java/com/danielkim/soundrecorder/EspressoScheduledRecordingsFragment.java index 33b779e4..07f2b88c 100644 --- a/app/src/androidTest/java/com/danielkim/soundrecorder/EspressoScheduledRecordingsFragment.java +++ b/app/src/androidTest/java/com/danielkim/soundrecorder/EspressoScheduledRecordingsFragment.java @@ -24,7 +24,6 @@ import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.action.ViewActions.click; import static android.support.test.espresso.action.ViewActions.longClick; -import static android.support.test.espresso.action.ViewActions.scrollTo; import static android.support.test.espresso.action.ViewActions.swipeLeft; import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist; import static android.support.test.espresso.assertion.ViewAssertions.matches; @@ -80,9 +79,7 @@ public void addScheduledRecordingCorrect() { // Delete the scheduled recording. onView(withText("23:00")).perform(longClick()); - ViewInteraction appCompatButton3 = onView( - Matchers.allOf(withId(android.R.id.button1), withText("OK"))); - appCompatButton3.perform(scrollTo(), click()); + onView(withText("OK")).perform(click()); // Check that the scheduled recording is no longer in the list. onView(withText("23:00")).check(doesNotExist()); @@ -124,9 +121,7 @@ public void addScheduledRecording3Minutes() { // Delete the scheduled recording. onView(withText("23:00")).perform(longClick()); - ViewInteraction appCompatButton3 = onView( - Matchers.allOf(withId(android.R.id.button1), withText("OK"))); - appCompatButton3.perform(scrollTo(), click()); + onView(withText("OK")).perform(click()); // Check that the scheduled recording is no longer in the list. onView(withText("23:00")).check(doesNotExist()); diff --git a/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java b/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java index f7ba3c33..ab2b321d 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java +++ b/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java @@ -184,6 +184,7 @@ public void onServiceConnected(ComponentName componentName, IBinder iBinder) { recordingService = ((RecordingService.LocalBinder) iBinder).getService(); serviceConnected = true; if (recordFragment != null) { + Log.d(TAG, "MainActivity - Fragment is not null"); recordFragment.serviceConnection(true); } recordingService.setOnRecordingStatusChangedListener(onScheduledRecordingListener); diff --git a/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java b/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java index 11ea5707..00e8a2d4 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java +++ b/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java @@ -117,7 +117,9 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, mRecordButton = (FloatingActionButton) recordView.findViewById(R.id.btnRecord); mRecordButton.setColorNormal(ContextCompat.getColor(getActivity(), R.color.primary)); mRecordButton.setColorPressed(ContextCompat.getColor(getActivity(), R.color.primary_dark)); - mRecordButton.setEnabled(serviceOperations.isServiceConnected()); + if (serviceOperations != null) { + mRecordButton.setEnabled(serviceOperations.isServiceConnected()); + } mRecordButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { diff --git a/gradle.properties b/gradle.properties index 1d3591c8..72f22c7e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,6 +11,7 @@ # The setting is particularly useful for tweaking memory settings. # Default value: -Xmx10248m -XX:MaxPermSize=256m # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 +org.gradle.jvmargs=-Xmx1536M # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit From c260a019cfed79310334c8b719111215cbea0f38 Mon Sep 17 00:00:00 2001 From: iClaude Date: Sun, 22 Oct 2017 17:54:50 +0200 Subject: [PATCH 75/96] API 22 bug fixes --- .../soundrecorder/activities/MainActivity.java | 2 -- .../fragments/DatePickerFragment.java | 15 +++++++++++++++ .../soundrecorder/fragments/RecordFragment.java | 15 +++++++++++++++ .../fragments/TimePickerFragment.java | 15 +++++++++++++++ app/src/main/res/values-v21/styles.xml | 2 +- app/src/main/res/values/styles.xml | 2 +- 6 files changed, 47 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java b/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java index ab2b321d..2c6468d1 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java +++ b/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java @@ -35,7 +35,6 @@ public class MainActivity extends AppCompatActivity implements RecordFragment.Se @Override protected void onCreate(Bundle savedInstanceState) { - Log.d(TAG, "onCreate: called"); super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); @@ -184,7 +183,6 @@ public void onServiceConnected(ComponentName componentName, IBinder iBinder) { recordingService = ((RecordingService.LocalBinder) iBinder).getService(); serviceConnected = true; if (recordFragment != null) { - Log.d(TAG, "MainActivity - Fragment is not null"); recordFragment.serviceConnection(true); } recordingService.setOnRecordingStatusChangedListener(onScheduledRecordingListener); diff --git a/app/src/main/java/com/danielkim/soundrecorder/fragments/DatePickerFragment.java b/app/src/main/java/com/danielkim/soundrecorder/fragments/DatePickerFragment.java index 1a0b0520..259835eb 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/fragments/DatePickerFragment.java +++ b/app/src/main/java/com/danielkim/soundrecorder/fragments/DatePickerFragment.java @@ -1,5 +1,7 @@ package com.danielkim.soundrecorder.fragments; +import android.annotation.TargetApi; +import android.app.Activity; import android.app.DatePickerDialog; import android.app.Dialog; import android.app.DialogFragment; @@ -40,6 +42,7 @@ public Dialog onCreateDialog(Bundle savedInstanceState) { return new DatePickerDialog(getActivity(), this, year, month, day); } + @TargetApi(23) @Override public void onAttach(Context context) { super.onAttach(context); @@ -51,6 +54,18 @@ public void onAttach(Context context) { } } + @SuppressWarnings("deprecation") + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + + try { + listener = (MyOnDateSetListener) activity; + } catch (ClassCastException e) { + throw new ClassCastException(activity.toString() + "must implement DatePickerFragment.MyOnDateSetListener"); + } + } + @Override public void onDateSet(DatePicker datePicker, int year, int month, int day) { if (listener != null) { diff --git a/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java b/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java index 00e8a2d4..866c7b24 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java +++ b/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java @@ -1,6 +1,8 @@ package com.danielkim.soundrecorder.fragments; import android.Manifest; +import android.annotation.TargetApi; +import android.app.Activity; import android.app.Fragment; import android.content.Context; import android.content.pm.PackageManager; @@ -70,6 +72,7 @@ public interface ServiceOperations { boolean isServiceRecording(); } + @TargetApi(23) @Override public void onAttach(Context context) { super.onAttach(context); @@ -81,6 +84,18 @@ public void onAttach(Context context) { } } + @SuppressWarnings("deprecation") + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + + try { + serviceOperations = (ServiceOperations) activity; + } catch (ClassCastException e) { + throw new ClassCastException(activity.toString() + " must implement ServiceOperations"); + } + } + /** * Use this factory method to create a new instance of * this fragment using the provided parameters. diff --git a/app/src/main/java/com/danielkim/soundrecorder/fragments/TimePickerFragment.java b/app/src/main/java/com/danielkim/soundrecorder/fragments/TimePickerFragment.java index 19771b52..5120d5d6 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/fragments/TimePickerFragment.java +++ b/app/src/main/java/com/danielkim/soundrecorder/fragments/TimePickerFragment.java @@ -1,5 +1,7 @@ package com.danielkim.soundrecorder.fragments; +import android.annotation.TargetApi; +import android.app.Activity; import android.app.Dialog; import android.app.DialogFragment; import android.app.TimePickerDialog; @@ -42,6 +44,7 @@ public Dialog onCreateDialog(Bundle savedInstanceState) { return new TimePickerDialog(getActivity(), this, hour, minute, DateFormat.is24HourFormat(getActivity())); } + @TargetApi(23) @Override public void onAttach(Context context) { super.onAttach(context); @@ -53,6 +56,18 @@ public void onAttach(Context context) { } } + @SuppressWarnings("deprecation") + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + + try { + listener = (TimePickerFragment.MyOnTimeSetListener) activity; + } catch (ClassCastException e) { + throw new ClassCastException(activity.toString() + "must implement TimePickerFragment.MyOnTimeSetListener"); + } + } + @Override public void onTimeSet(TimePicker timePicker, int hourOfDay, int minute) { if (listener != null) { diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml index 3389db8a..c67cc36a 100644 --- a/app/src/main/res/values-v21/styles.xml +++ b/app/src/main/res/values-v21/styles.xml @@ -4,7 +4,7 @@