Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
990c095
Merge pull request #15 from Suphax/master
mafik Jan 22, 2021
b06834e
Android SDK Update, Background Recording Support, Permission Fixes, U…
jenisha010 Mar 19, 2024
1e4816a
Merge pull request #21 from jenisha010/master
mafik Mar 27, 2024
7e74b88
Ignore .idea files
mafik Jun 5, 2025
8fb6b5b
Bump gradle wrapper
mafik Jun 5, 2025
791a3a6
Remove FakeService
mafik Jun 5, 2025
542b320
Migrate build config generation from gradle.properties to build.gradle
mafik Jun 5, 2025
e67ebbd
Bump minimum SDK level to 30
mafik Jun 5, 2025
526dddd
Remove more obsolete files
mafik Jun 5, 2025
b21ee7a
Fix UI responsiveness
mafik Jun 5, 2025
63f5b58
Disable constant logging
mafik Jun 5, 2025
8da0892
Fix crash when re-enabling listening
mafik Jun 5, 2025
a7cc7a6
Switch app links to GitHub/F-Droid (from Google Play)
mafik Jun 5, 2025
2358d2a
Bump version
mafik Jun 5, 2025
b496512
Fastlane metadata
mafik Jun 5, 2025
6946f49
feat: Complete UI overhaul and feature implementation
ElliotBadinger Aug 18, 2025
5ce8a1a
feat: Implement Recordings Manager and Fix Critical Warnings
ElliotBadinger Aug 18, 2025
5461ccc
refactor: Complete UI Polish and Crash Fixes
ElliotBadinger Aug 18, 2025
07572c2
feat: Implement comprehensive UI/UX overhaul and critical bug fixes
ElliotBadinger Aug 18, 2025
e34b082
feat: Implement time-based auto-save, fix playback, and add How-To guide
ElliotBadinger Aug 19, 2025
0744739
feat(auto-save): use timestamped filenames (Auto-save_YYYY-MM-DD_HH-m…
ElliotBadinger Aug 19, 2025
6043bcb
feat(memory): replace chunked lists with direct ring buffer for low-G…
ElliotBadinger Aug 19, 2025
6046024
feat(recording): switch to AAC-LC (.m4a) and save via MediaStore with…
ElliotBadinger Aug 19, 2025
9649c84
feat(ui): add Save Clip flow, sync listening toggle, and recordings l…
ElliotBadinger Aug 19, 2025
3c3b7cf
test: Add comprehensive unit and instrumentation tests
ElliotBadinger Aug 19, 2025
24b44f8
Refactor: Abstract system clock for testability and update gitignore
ElliotBadinger Aug 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 36 additions & 26 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,26 +1,36 @@
# 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

# Proguard folder generated by Eclipse
proguard/

# Log Files
*.log
# 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

# Proguard folder generated by Eclipse
proguard/

# Log Files
*.log

# IDEs
.idea/
.vscode/

# Ignore Markdown files
*.md

# Ignore Claude agent files
.claude/
53 changes: 38 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,44 @@
Echo
====
# Echo - Never Miss a Moment

Time travelling recorder for Android.
It is free/libre and gratis software.
Echo is a modern Android application that continuously records audio in the background, allowing you to go back in time and save moments that have already happened. Whether it's a brilliant idea, a funny quote, or an important note, Echo ensures you never miss it.

Download
---
## Features

* [F-Droid](https://f-droid.org/repository/browse/?fdid=eu.mrogalski.saidit)
* **Continuous Background Recording:** Echo runs silently in the background, keeping a rolling buffer of the last few hours of audio.
* **Save Clips from the Past:** Instantly save audio clips of various lengths from the buffered memory.
* **Auto-Save:** Automatically save recordings when the memory buffer is full, ensuring you never lose important audio.
* **Modern, Intuitive Interface:** A clean, professional design built with Material You principles.
* **Customizable Settings:** Adjust the audio quality and memory usage to fit your needs.

Building
---
## Getting Started

1. Install gradle-1.10 (version is important)
1. Install SDK platform API 21 and 21.0.2 build-tools, with either [sdkmanager](https://developer.android.com/studio/command-line/sdkmanager) or [Android Studio](https://developer.android.com/studio)
1. Create a Key Store - [Instructions](http://stackoverflow.com/questions/3997748/how-can-i-create-a-keystore)
1. Fill Key Store details in `SaidIt/build.gradle`
1. From this directory run `gradle installDebug` - to install it on a phone or `gradle assembleRelease` - to generate signed APK
### Prerequisites

If you had any issues and fixed them, please correct these instructions in your fork!
* Android Studio
* Java Development Kit (JDK)

### Building the Project

1. **Clone the repository:**
```bash
git clone https://github.com/mafik/echo.git
```
2. **Open the project in Android Studio.**
3. **Create a `local.properties` file** in the root of the project and add the following line, pointing to your Android SDK location:
```
sdk.dir=/path/to/your/android/sdk
```
4. **Build the project:**
* From the command line, run:
```bash
./gradlew assembleDebug
```
* Or, use the "Build" menu in Android Studio.

## Contributing

We welcome contributions! Please feel free to open an issue or submit a pull request.

## Future Development

For a detailed roadmap of planned features and improvements, please see the [`spec.md`](spec.md) file.
46 changes: 32 additions & 14 deletions SaidIt/build.gradle
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
buildscript {
repositories {
google()
mavenCentral()
maven { url "https://repo.maven.apache.org/maven2" }
}
dependencies {
classpath 'com.android.tools.build:gradle:0.11.+'
classpath 'com.android.tools.build:gradle:8.12.1'
}
}
apply plugin: 'android'
apply plugin: 'com.android.application'

repositories {
jcenter()
mavenCentral()
maven { url "https://maven.google.com" }
}

android {
compileSdkVersion 21
buildToolsVersion "21.0.2"
namespace 'eu.mrogalski.saidit'
compileSdk 34

defaultConfig{
applicationId "eu.mrogalski.saidit"
minSdk 30
targetSdk 34
versionCode 15
versionName "2.0.0"
}

signingConfigs {
release {
Expand All @@ -28,27 +38,35 @@ android {

buildTypes {
release {
runProguard true
minifyEnabled true
proguardFile file('proguard.cfg')
proguardFile getDefaultProguardFile('proguard-android-optimize.txt')
signingConfig signingConfigs.release
}

debug {
signingConfig signingConfigs.release
//signingConfig signingConfigs.release
}
}
lintOptions {
// Or, if you prefer, you can continue to check for errors in release builds,
// but continue the build even when errors are found:
lint {
abortOnError false
}
buildFeatures {
buildConfig true
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}

}

dependencies {
compile fileTree(dir: 'libs', include: '*.jar')
compile 'com.android.support:appcompat-v7:21.0.3'
compile 'com.nineoldandroids:library:2.4.0'
compile 'com.android.support:support-v4:21.0.3'
implementation fileTree(dir: 'libs', include: '*.jar')
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.11.0'
implementation 'com.getkeepsafe.taptargetview:taptargetview:1.13.3'
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-core:5.11.0'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package eu.mrogalski.saidit;

import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;

import org.junit.Test;
import org.junit.runner.RunWith;

import static org.junit.Assert.*;

/**
* Instrumented test, which will execute on an Android device.
*
* @see Testing documentation
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("eu.mrogalski.saidit", appContext.getPackageName());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package eu.mrogalski.saidit;

import androidx.test.espresso.action.ViewActions;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;

@RunWith(AndroidJUnit4.class)
public class SaidItFragmentTest {

@Rule
public ActivityScenarioRule<SaidItActivity> activityRule =
new ActivityScenarioRule<>(SaidItActivity.class);

@Test
public void testSaveClipFlow_showsProgressDialog() {
// 1. Click the "Save Clip" button to show the bottom sheet
onView(withId(R.id.save_clip_button)).perform(ViewActions.click());

// 2. In the bottom sheet, click a duration button.
// We'll assume the layout for the bottom sheet has buttons with text like "15 seconds"
// Let's click a common one, like "30 seconds"
onView(withText("30 seconds")).perform(ViewActions.click());

// 3. Verify that the "Saving Recording" progress dialog appears.
// The dialog is a system window, so we check for the title text.
onView(withText("Saving Recording")).check(matches(isDisplayed()));
}
}
59 changes: 41 additions & 18 deletions SaidIt/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="eu.mrogalski.saidit"
android:versionCode="14"
android:versionName="1.3.39"
android:installLocation="internalOnly">

<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="19" />
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<!-- Basic functionality -->
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

<supports-screens
android:largeScreens="true"
Expand All @@ -26,7 +23,8 @@
android:theme="@style/Theme" >
<activity
android:name=".SaidItActivity"
android:label="@string/app_name" >
android:label="@string/app_name"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
Expand All @@ -49,16 +47,13 @@

<service
android:name=".SaidItService"
android:foregroundServiceType="microphone"
android:enabled="true"
android:exported="true" >
</service>
<service
android:name=".FakeService"
android:enabled="true"
android:exported="false" >
android:exported="false">
</service>

<receiver android:name=".BroadcastReceiver" >
<receiver android:name=".BroadcastReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
Expand All @@ -71,6 +66,34 @@
android:name="android.support.PARENT_ACTIVITY"
android:value="SaidItActivity" />
</activity>

<activity
android:name=".RecordingsActivity"
android:parentActivityName=".SaidItActivity" >
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="SaidItActivity" />
</activity>

<activity
android:name=".HowToActivity"
android:parentActivityName=".SaidItActivity" >
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="SaidItActivity" />
</activity>

<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/path_provider">

</meta-data>
</provider>
</application>

</manifest>
Loading