├── .gitignore ├── LICENSE ├── README.md ├── app ├── build.gradle ├── build │ └── .gitignore ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── ic_launcher-playstore.png │ ├── ic_launcher-web.png │ ├── java │ └── at │ │ └── andreasrohner │ │ └── spartantimelapserec │ │ ├── DeviceStatusReceiver.java │ │ ├── ForegroundService.java │ │ ├── GithubStar.java │ │ ├── MainActivity.java │ │ ├── PowerSavingReceiver.java │ │ ├── PreviewActivity.java │ │ ├── ScheduleReceiver.java │ │ ├── ServiceHelper.java │ │ ├── SettingsCommon.java │ │ ├── SettingsFragment.java │ │ ├── data │ │ ├── RecMode.java │ │ └── RecSettings.java │ │ ├── preference │ │ ├── DateTimePreference.java │ │ ├── EditSummaryPreference.java │ │ ├── IntervalPickerPreference.java │ │ ├── IpInformation.java │ │ ├── NoKBEditTextPreference.java │ │ └── SeekBarPreference.java │ │ ├── recorder │ │ ├── ImageRecorder.java │ │ ├── PowerSavingImageRecorder.java │ │ ├── Recorder.java │ │ ├── VideoRecorder.java │ │ └── VideoTimeLapseRecorder.java │ │ ├── rest │ │ ├── HttpOutput.java │ │ ├── HttpThread.java │ │ ├── ListFolderHtml.java │ │ ├── ListFolderPlain.java │ │ ├── ReplyCode.java │ │ ├── RestService.java │ │ └── TcpListener.java │ │ └── sensor │ │ ├── CameraSettings.java │ │ ├── MuteShutter.java │ │ └── OrientationSensor.java │ └── res │ ├── drawable │ ├── ic_camera.xml │ ├── ic_launcher_foreground.xml │ ├── ic_launcher_monochrome.xml │ ├── ic_radio_button_checked_24px.xml │ ├── ic_radio_button_checked_disabled_24px.xml │ ├── ic_stop_circle_24px.xml │ ├── ic_stop_circle_disabled_24px.xml │ ├── ic_visibility_24px.xml │ └── ic_visibility_disabled_24px.xml │ ├── layout │ ├── dialog_date_preference.xml │ ├── dialog_date_widget_preference.xml │ ├── dialog_intervalpicker_preference.xml │ └── dialog_seekbar_preference.xml │ ├── menu │ └── main.xml │ ├── mipmap-anydpi-v26 │ └── ic_launcher.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── raw │ ├── favicon.ico │ ├── help.txt │ └── index.html │ ├── values-de │ ├── arrays.xml │ └── strings.xml │ ├── values-night │ └── themes.xml │ ├── values-tr │ └── strings.xml │ ├── values │ ├── arrays.xml │ ├── attrs.xml │ ├── colors.xml │ ├── dimens.xml │ ├── ic_launcher_background.xml │ ├── strings.xml │ └── themes.xml │ └── xml │ └── preferences.xml ├── build.gradle ├── fastlane └── metadata │ └── android │ ├── de-DE │ ├── changelogs │ │ ├── 10.txt │ │ ├── 11.txt │ │ ├── 12.txt │ │ ├── 13.txt │ │ ├── 14.txt │ │ ├── 15.txt │ │ ├── 16.txt │ │ ├── 17.txt │ │ └── 18.txt │ ├── full_description.txt │ ├── short_description.txt │ └── title.txt │ ├── en-US │ ├── changelogs │ │ ├── 10.txt │ │ ├── 11.txt │ │ ├── 12.txt │ │ ├── 13.txt │ │ ├── 14.txt │ │ ├── 15.txt │ │ ├── 16.txt │ │ ├── 17.txt │ │ ├── 18.txt │ │ └── 19.txt │ ├── full_description.txt │ ├── images │ │ ├── icon.png │ │ └── phoneScreenshots │ │ │ └── 01.png │ ├── short_description.txt │ └── title.txt │ └── tr-TR │ ├── full_description.txt │ ├── short_description.txt │ └── title.txt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/* 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | .cxx 10 | local.properties 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
Send a coffee to 
 2 | woheller69@t-online.de 
 3 | 
 4 | 
 5 |   
 6 | Or via this link (with fees)
 7 | 
8 | 9 | 10 | | **RadarWeather** | **Gas Prices** | **Smart Eggtimer** | 11 | |:---:|:---:|:---:| 12 | | [](https://f-droid.org/packages/org.woheller69.weather/) | [](https://f-droid.org/packages/org.woheller69.spritpreise/) | [](https://f-droid.org/packages/org.woheller69.eggtimer/) | 13 | | **Bubble** | **hEARtest** | **GPS Cockpit** | 14 | | [](https://f-droid.org/packages/org.woheller69.level/) | [](https://f-droid.org/packages/org.woheller69.audiometry/) | [](https://f-droid.org/packages/org.woheller69.gpscockpit/) | 15 | | **Audio Analyzer** | **LavSeeker** | **TimeLapseCam** | 16 | | [](https://f-droid.org/packages/org.woheller69.audio_analyzer_for_android/) |[](https://f-droid.org/packages/org.woheller69.lavatories/) | [](https://f-droid.org/packages/org.woheller69.TimeLapseCam/) | 17 | | **Arity** | **Cirrus** | **solXpect** | 18 | | [](https://f-droid.org/packages/org.woheller69.arity/) | [](https://f-droid.org/packages/org.woheller69.omweather/) | [](https://f-droid.org/packages/org.woheller69.solxpect/) | 19 | | **gptAssist** | **dumpSeeker** | **huggingAssist** | 20 | | [](https://f-droid.org/packages/org.woheller69.gptassist/) | [](https://f-droid.org/packages/org.woheller69.dumpseeker/) | [](https://f-droid.org/packages/org.woheller69.hugassist/) | 21 | | **FREE Browser** | **whoBIRD** | **PeakOrama** | 22 | | [](https://f-droid.org/packages/org.woheller69.browser/) | [](https://f-droid.org/packages/org.woheller69.whobird/) | [](https://f-droid.org/packages/org.woheller69.PeakOrama/) | 23 | | **Whisper** | **Seamless** | | 24 | | [](https://f-droid.org/packages/org.woheller69.whisper/) | [](https://f-droid.org/packages/org.woheller69.seemless/) | | 25 | 26 | TimeLapseCam 27 | ======================== 28 | 29 | 30 | 31 | Get it on F-Droid 32 | 33 | Minimalistic android app that records time lapse videos in the background 34 | with the screen turned off. Time lapse videos can be saved in various resolutions 35 | as a MP4 video file, or as individual JPEG files. If possible the shutter sound 36 | of the camera is muted, so it can be used as a spy camera. While recording 37 | there is no preview visible and the screen can be turned off. 38 | There is no recording limit, but the app will stop recording if battery 39 | level or disk space is low. 40 | 41 | ## Features 42 | 43 | * Record normal video (MP4) 44 | * Record time lapse video (MP4/JPEG) 45 | * Select various resolutions 46 | * Select camera to use 47 | * Schedule recording on specific date 48 | * Preview with selected resolution 49 | * Disable shutter sound 50 | 51 | ## License 52 | 53 | Forked from zeitgeist87/SpartanTimeLapseRecorder which is published under GPL 3.0 54 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | } 4 | 5 | android { 6 | compileSdk 34 7 | 8 | defaultConfig { 9 | applicationId "org.woheller69.TimeLapseCam" 10 | minSdk 23 11 | targetSdk 34 12 | versionCode 19 13 | versionName "1.9" 14 | 15 | } 16 | 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | compileOptions { 24 | sourceCompatibility JavaVersion.VERSION_1_8 25 | targetCompatibility JavaVersion.VERSION_1_8 26 | } 27 | } 28 | 29 | dependencies { 30 | 31 | implementation "androidx.core:core:1.9.0" //needed for broadcast receiver 32 | implementation 'androidx.appcompat:appcompat:1.5.1' 33 | implementation 'com.google.android.material:material:1.7.0' 34 | 35 | } -------------------------------------------------------------------------------- /app/build/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 32 | 36 | 37 | 38 | 39 | 40 | 41 | 45 | 46 | 47 | 51 | 52 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woheller69/TimeLapseCamera/dbec6562463db540398f4b3b4c1442e5fecfb0c3/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woheller69/TimeLapseCamera/dbec6562463db540398f4b3b4c1442e5fecfb0c3/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/at/andreasrohner/spartantimelapserec/DeviceStatusReceiver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Spartan Time Lapse Recorder - Minimalistic android time lapse recording app 3 | * Copyright (C) 2014 Andreas Rohner 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package at.andreasrohner.spartantimelapserec; 20 | 21 | import android.content.BroadcastReceiver; 22 | import android.content.Context; 23 | import android.content.Intent; 24 | import android.content.SharedPreferences; 25 | import android.preference.PreferenceManager; 26 | import android.util.Log; 27 | 28 | public class DeviceStatusReceiver extends BroadcastReceiver { 29 | 30 | @Override 31 | public void onReceive(Context context, Intent intent) { 32 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 33 | 34 | boolean stopOnLowBattery = prefs.getBoolean("pref_stop_low_battery", true); 35 | boolean stopOnLowStorage = prefs.getBoolean("pref_stop_low_storage", true); 36 | /* 37 | * battery or storage is low if we don't stop the recording the mp4 38 | * files get corrupted and are not playable any more 39 | */ 40 | if ((stopOnLowBattery && intent.getAction().equals(Intent.ACTION_BATTERY_LOW)) || (stopOnLowStorage && intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_LOW)) || intent.getAction().equals(Intent.ACTION_SHUTDOWN)){ 41 | Intent stopintent = new Intent(context, ForegroundService.class); 42 | stopintent.setAction(ForegroundService.ACTION_STOP_SERVICE); 43 | context.startService(stopintent); 44 | } 45 | 46 | //if (intent.getAction().equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) Log.d("DeviceStatusReceiver","ACTION_AIRPLANE_MODE_CHANGED"); //for testing: Activate filter in MainActivity 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/at/andreasrohner/spartantimelapserec/ForegroundService.java: -------------------------------------------------------------------------------- 1 | package at.andreasrohner.spartantimelapserec; 2 | 3 | import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST; 4 | 5 | import android.app.AlarmManager; 6 | import android.app.Notification; 7 | import android.app.NotificationChannel; 8 | import android.app.NotificationManager; 9 | import android.app.PendingIntent; 10 | import android.app.Service; 11 | import android.content.Context; 12 | import android.content.Intent; 13 | 14 | import android.content.pm.ServiceInfo; 15 | import android.net.Uri; 16 | import android.os.Build; 17 | import android.os.Build.VERSION; 18 | import android.os.Build.VERSION_CODES; 19 | import android.os.Handler; 20 | import android.os.HandlerThread; 21 | import android.os.IBinder; 22 | 23 | import android.os.Message; 24 | import android.os.PowerManager; 25 | import android.os.PowerManager.WakeLock; 26 | import android.preference.PreferenceManager; 27 | import android.util.Log; 28 | 29 | import androidx.core.app.NotificationCompat; 30 | import androidx.core.app.NotificationCompat.Builder; 31 | 32 | import java.io.File; 33 | 34 | import at.andreasrohner.spartantimelapserec.data.RecSettings; 35 | import at.andreasrohner.spartantimelapserec.recorder.Recorder; 36 | 37 | public class ForegroundService extends Service implements Handler.Callback { 38 | 39 | public static final String ACTION_STOP_SERVICE = "TimeLapse.action.STOP_SERVICE"; 40 | public static boolean mIsRunning = false; 41 | private RecSettings settings; 42 | private Recorder recorder; 43 | private HandlerThread handlerThread; 44 | private WakeLock mWakeLock; 45 | private static statusListener listener; 46 | private NotificationManager mNotificationManager; 47 | 48 | public interface statusListener { 49 | void onServiceStatusChange(boolean status); 50 | } 51 | 52 | public static void registerStatusListener(statusListener l){ 53 | listener = l; 54 | } 55 | 56 | @Override 57 | public IBinder onBind(Intent intent) { 58 | return null; 59 | } 60 | 61 | @Override 62 | public int onStartCommand(Intent intent, int flags, int startId) { 63 | if (intent == null || !ACTION_STOP_SERVICE.equals(intent.getAction())) { 64 | initNotif(); 65 | mIsRunning = true; 66 | 67 | settings = new RecSettings(); 68 | settings.load(getApplicationContext(), PreferenceManager.getDefaultSharedPreferences(getApplicationContext())); 69 | 70 | if (settings.isSchedRecEnabled() && settings.getSchedRecTime() > System.currentTimeMillis() + 10000) { 71 | AlarmManager alarmMgr = (AlarmManager) getSystemService(Context.ALARM_SERVICE); 72 | Intent newintent = new Intent(getApplicationContext(), ScheduleReceiver.class); 73 | PendingIntent alarmIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, newintent, PendingIntent.FLAG_IMMUTABLE); 74 | alarmMgr.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, settings.getSchedRecTime(), alarmIntent); 75 | 76 | } else { 77 | 78 | PowerManager mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); 79 | mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName()); 80 | mWakeLock.acquire(); 81 | 82 | handlerThread = new HandlerThread("recordingVideo"); 83 | handlerThread.start(); 84 | 85 | Context context = getApplicationContext(); 86 | Handler handler = new Handler(handlerThread.getLooper(), this); 87 | 88 | recorder = Recorder.getInstance(settings, context, 89 | handler, mWakeLock); 90 | 91 | handler.post(new Runnable() { 92 | @Override 93 | public void run() { 94 | recorder.start(); 95 | } 96 | }); 97 | updateNotif(); 98 | } 99 | 100 | if (listener!=null) listener.onServiceStatusChange(true); 101 | return START_STICKY; 102 | } else { 103 | AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); 104 | intent = new Intent(this, ScheduleReceiver.class); 105 | PendingIntent alarmIntent; 106 | alarmIntent = PendingIntent.getBroadcast(getApplicationContext(),0,intent,PendingIntent.FLAG_IMMUTABLE); 107 | alarmManager.cancel(alarmIntent); 108 | 109 | stop(); 110 | return START_NOT_STICKY; 111 | } 112 | } 113 | 114 | @Override 115 | public void onCreate() { 116 | 117 | } 118 | 119 | @Override 120 | public void onDestroy() { 121 | super.onDestroy(); 122 | } 123 | 124 | 125 | private void stop() { 126 | 127 | File projectDir = null; 128 | 129 | if (recorder != null) { 130 | recorder.stop(); 131 | projectDir = recorder.getOutputDir(); 132 | recorder = null; 133 | } 134 | 135 | if (mWakeLock != null && mWakeLock.isHeld()) 136 | mWakeLock.release(); 137 | 138 | if (projectDir != null && projectDir.exists()) 139 | sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, 140 | Uri.fromFile(projectDir))); 141 | 142 | mIsRunning = false; 143 | if (listener!=null) listener.onServiceStatusChange(false); 144 | stopForeground(true); 145 | stopSelf(); 146 | } 147 | 148 | private static final int NOTIF_ID = 123; 149 | private static final String CHANNEL_ID = "TimeLapseID"; 150 | 151 | private void updateNotif(){ 152 | 153 | Intent intent = new Intent(this, MainActivity.class); 154 | intent.setAction(Intent.ACTION_MAIN); 155 | intent.addCategory(Intent.CATEGORY_LAUNCHER); 156 | PendingIntent pi = PendingIntent.getActivity(this, NOTIF_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); 157 | 158 | Notification notification = new Builder(this, CHANNEL_ID) 159 | .setSilent(true) 160 | .setOnlyAlertOnce(true) 161 | .setPriority(NotificationCompat.PRIORITY_DEFAULT) // For N and below 162 | .setContentIntent(pi) 163 | .setSmallIcon(R.drawable.ic_camera) 164 | .setAutoCancel(false) 165 | .setOngoing(true) 166 | .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) 167 | .setContentText(getString(R.string.info_recording_running)) 168 | .setContentTitle(getString(R.string.app_name)).build(); 169 | 170 | mNotificationManager.notify(NOTIF_ID,notification); 171 | } 172 | 173 | private void initNotif() { 174 | 175 | mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 176 | if (VERSION.SDK_INT >= VERSION_CODES.O) { 177 | mNotificationManager.createNotificationChannel(new NotificationChannel(CHANNEL_ID, "TimeLapse", NotificationManager.IMPORTANCE_DEFAULT)); 178 | } 179 | Intent intent = new Intent(this, MainActivity.class); 180 | intent.setAction(Intent.ACTION_MAIN); 181 | intent.addCategory(Intent.CATEGORY_LAUNCHER); 182 | PendingIntent pi = PendingIntent.getActivity(this, NOTIF_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); 183 | 184 | // For N and below 185 | Notification notification = new Builder(this, CHANNEL_ID) 186 | .setSilent(true) 187 | .setOnlyAlertOnce(true) 188 | .setPriority(NotificationCompat.PRIORITY_DEFAULT) // For N and below 189 | .setContentIntent(pi) 190 | .setSmallIcon(R.drawable.ic_camera) 191 | .setAutoCancel(false) 192 | .setOngoing(true) 193 | .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) 194 | .setContentText(getString(R.string.notification_preparing)) 195 | .setContentTitle(getString(R.string.app_name)).build(); 196 | 197 | if (VERSION.SDK_INT >= VERSION_CODES.Q) { 198 | startForeground(NOTIF_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA); 199 | } else { 200 | startForeground(NOTIF_ID, notification); 201 | } 202 | 203 | } 204 | 205 | @Override 206 | public boolean handleMessage(Message m) { 207 | String status = m.getData().getString("status"); 208 | String tag = m.getData().getString("tag"); 209 | String msg = m.getData().getString("msg"); 210 | 211 | if ("error".equals(status)) { 212 | Log.e(tag, "Error: " + msg); 213 | stop(); 214 | } else if ("success".equals(status)){ 215 | Log.e(tag, "Success"); 216 | stop(); 217 | } 218 | 219 | return true; 220 | } 221 | 222 | } 223 | 224 | -------------------------------------------------------------------------------- /app/src/main/java/at/andreasrohner/spartantimelapserec/GithubStar.java: -------------------------------------------------------------------------------- 1 | package at.andreasrohner.spartantimelapserec; 2 | 3 | import android.content.Context; 4 | import android.content.DialogInterface; 5 | import android.content.Intent; 6 | import android.content.SharedPreferences; 7 | import android.net.Uri; 8 | import android.preference.PreferenceManager; 9 | 10 | import androidx.appcompat.app.AlertDialog; 11 | 12 | public class GithubStar { 13 | public static void setAskForStar(boolean askForStar, Context context){ 14 | SharedPreferences prefManager = PreferenceManager.getDefaultSharedPreferences(context); 15 | SharedPreferences.Editor editor = prefManager.edit(); 16 | editor.putBoolean("askForStar", askForStar); 17 | editor.apply(); 18 | } 19 | 20 | static boolean shouldShowStarDialog(Context context) { 21 | SharedPreferences prefManager = PreferenceManager.getDefaultSharedPreferences(context); 22 | int versionCode = prefManager.getInt("versionCode",0); 23 | boolean askForStar=prefManager.getBoolean("askForStar",true); 24 | 25 | if (prefManager.contains("versionCode") && BuildConfig.VERSION_CODE>versionCode && askForStar){ //not at first start, only after upgrade and only if use has not yet given a star or has declined 26 | SharedPreferences.Editor editor = prefManager.edit(); 27 | editor.putInt("versionCode", BuildConfig.VERSION_CODE); 28 | editor.apply(); 29 | return true; 30 | } else { 31 | SharedPreferences.Editor editor = prefManager.edit(); 32 | editor.putInt("versionCode", BuildConfig.VERSION_CODE); 33 | editor.apply(); 34 | return false; 35 | } 36 | } 37 | 38 | static void starDialog(Context context, String url){ 39 | SharedPreferences prefManager = PreferenceManager.getDefaultSharedPreferences(context); 40 | if (prefManager.getBoolean("askForStar",true)) { 41 | AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context); 42 | alertDialogBuilder.setMessage(R.string.dialog_StarOnGitHub); 43 | alertDialogBuilder.setPositiveButton(context.getString(R.string.dialog_OK_button), new DialogInterface.OnClickListener() { 44 | @Override 45 | public void onClick(DialogInterface dialog, int which) { 46 | context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url))); 47 | setAskForStar(false, context); 48 | } 49 | }); 50 | alertDialogBuilder.setNegativeButton(context.getString(R.string.dialog_NO_button), new DialogInterface.OnClickListener() { 51 | @Override 52 | public void onClick(DialogInterface dialog, int which) { 53 | setAskForStar(false, context); 54 | } 55 | }); 56 | alertDialogBuilder.setNeutralButton(context.getString(R.string.dialog_Later_button), null); 57 | 58 | AlertDialog alertDialog = alertDialogBuilder.create(); 59 | alertDialog.show(); 60 | } 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/at/andreasrohner/spartantimelapserec/MainActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Spartan Time Lapse Recorder - Minimalistic android time lapse recording app 3 | * Copyright (C) 2014 Andreas Rohner 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package at.andreasrohner.spartantimelapserec; 20 | 21 | import android.Manifest; 22 | import android.app.AlarmManager; 23 | import android.content.BroadcastReceiver; 24 | import android.content.Context; 25 | import android.content.Intent; 26 | import android.content.IntentFilter; 27 | import android.content.SharedPreferences; 28 | import android.content.pm.PackageManager; 29 | import android.net.Uri; 30 | import android.os.Build; 31 | import android.os.Bundle; 32 | import android.preference.PreferenceManager; 33 | import android.provider.Settings; 34 | import android.view.Menu; 35 | import android.view.MenuItem; 36 | import android.widget.Toast; 37 | 38 | import androidx.appcompat.app.AppCompatActivity; 39 | import androidx.core.app.ActivityCompat; 40 | import androidx.core.content.ContextCompat; 41 | 42 | import at.andreasrohner.spartantimelapserec.data.RecSettings; 43 | import at.andreasrohner.spartantimelapserec.sensor.MuteShutter; 44 | 45 | public class MainActivity extends AppCompatActivity implements ForegroundService.statusListener { 46 | 47 | private static SettingsFragment settingsFragment; 48 | private static BroadcastReceiver broadcastReceiver; 49 | 50 | @Override 51 | protected void onCreate(Bundle savedInstanceState) { 52 | super.onCreate(savedInstanceState); 53 | 54 | if (broadcastReceiver==null) broadcastReceiver = new DeviceStatusReceiver(); 55 | IntentFilter filter = new IntentFilter(); 56 | filter.addAction("android.intent.action.ACTION_BATTERY_LOW"); 57 | filter.addAction("android.intent.action.ACTION_DEVICE_STORAGE_LOW"); 58 | filter.addAction("android.intent.action.ACTION_SHUTDOWN"); 59 | //filter.addAction("android.intent.action.AIRPLANE_MODE"); //for testing 60 | ContextCompat.registerReceiver(getApplicationContext(),broadcastReceiver, filter, ContextCompat.RECEIVER_EXPORTED); 61 | 62 | if (Build.VERSION.SDK_INT=Build.VERSION_CODES.TIRAMISU)) { 100 | //PERMISSION POST_NOTIFICATION is required and not tested here 101 | 102 | // Display the fragment as the main content. 103 | if (settingsFragment==null) { 104 | settingsFragment = new SettingsFragment(); 105 | settingsFragment.setRetainInstance(true); //do not recreate if orientation is changed 106 | } 107 | getFragmentManager().beginTransaction() 108 | .replace(android.R.id.content, settingsFragment) 109 | .commit(); 110 | 111 | } else Toast.makeText(this, getString(R.string.error_missing_permission), Toast.LENGTH_SHORT).show(); 112 | 113 | AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); 114 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) { 115 | if (!alarmManager.canScheduleExactAlarms()) { 116 | Intent intent = new Intent(); 117 | intent.setAction(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM); 118 | startActivity(intent); 119 | } 120 | } 121 | 122 | } 123 | 124 | public void actionStart(MenuItem item) { 125 | ServiceHelper helper = new ServiceHelper(getApplicationContext()); 126 | helper.start(true); 127 | 128 | invalidateOptionsMenu(); 129 | } 130 | 131 | @Override 132 | protected void onDestroy() { 133 | broadcastReceiver = null; 134 | super.onDestroy(); 135 | } 136 | 137 | public void actionStop(MenuItem item) { 138 | ServiceHelper helper = new ServiceHelper(getApplicationContext()); 139 | helper.stop(); 140 | 141 | invalidateOptionsMenu(); 142 | } 143 | 144 | public void actionGallery(MenuItem item) { 145 | Intent intent = new Intent(); 146 | intent.setAction(android.content.Intent.ACTION_VIEW); 147 | intent.setType("image/*"); 148 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 149 | startActivity(intent); 150 | } 151 | 152 | public void actionPreview(MenuItem item) { 153 | if (!ForegroundService.mIsRunning) { 154 | Intent intent = new Intent(MainActivity.this, PreviewActivity.class); 155 | startActivity(intent); 156 | } else { 157 | Toast.makeText(this, getString(R.string.info_recording_running), Toast.LENGTH_SHORT).show(); 158 | } 159 | } 160 | 161 | public void actionUnmuteAllStreams(MenuItem item) { 162 | MuteShutter mute = new MuteShutter(this); 163 | mute.maxAllStreams(); 164 | } 165 | 166 | public void actionAbout(MenuItem item) { 167 | startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/woheller69/timelapsecamera"))); 168 | } 169 | 170 | @Override 171 | public boolean onCreateOptionsMenu(Menu menu) { 172 | getMenuInflater().inflate(R.menu.main, menu); 173 | if (ForegroundService.mIsRunning){ 174 | menu.findItem(R.id.action_start).setEnabled(false); 175 | menu.findItem(R.id.action_start).setIcon(ContextCompat.getDrawable(this,R.drawable.ic_radio_button_checked_disabled_24px)); 176 | menu.findItem(R.id.action_preview).setEnabled(false); 177 | menu.findItem(R.id.action_preview).setIcon(ContextCompat.getDrawable(this,R.drawable.ic_visibility_disabled_24px)); 178 | 179 | RecSettings settings = new RecSettings(); 180 | settings.load(getApplicationContext(), PreferenceManager.getDefaultSharedPreferences(getApplicationContext())); 181 | if (settings.isSchedRecEnabled() && settings.getSchedRecTime() > System.currentTimeMillis()){ 182 | menu.findItem(R.id.action_stop).setEnabled(false); 183 | menu.findItem(R.id.action_stop).setIcon(ContextCompat.getDrawable(this,R.drawable.ic_stop_circle_disabled_24px)); 184 | } 185 | 186 | } else { 187 | menu.findItem(R.id.action_stop).setEnabled(false); 188 | menu.findItem(R.id.action_stop).setIcon(ContextCompat.getDrawable(this,R.drawable.ic_stop_circle_disabled_24px)); 189 | } 190 | return true; 191 | } 192 | 193 | @Override 194 | public void onServiceStatusChange(boolean status) { 195 | invalidateOptionsMenu(); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /app/src/main/java/at/andreasrohner/spartantimelapserec/PowerSavingReceiver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Spartan Time Lapse Recorder - Minimalistic android time lapse recording app 3 | * Copyright (C) 2014 Andreas Rohner 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package at.andreasrohner.spartantimelapserec; 20 | 21 | import android.content.BroadcastReceiver; 22 | import android.content.Context; 23 | import android.content.Intent; 24 | 25 | public class PowerSavingReceiver extends BroadcastReceiver { 26 | 27 | @Override 28 | public void onReceive(Context context, Intent intent) { 29 | Intent serviceIntent = new Intent(context, ForegroundService.class); 30 | serviceIntent.putExtra("powerSavingCallback", true); 31 | context.startService(serviceIntent); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/at/andreasrohner/spartantimelapserec/ScheduleReceiver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Spartan Time Lapse Recorder - Minimalistic android time lapse recording app 3 | * Copyright (C) 2014 Andreas Rohner 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package at.andreasrohner.spartantimelapserec; 20 | 21 | import android.content.BroadcastReceiver; 22 | import android.content.Context; 23 | import android.content.Intent; 24 | 25 | public class ScheduleReceiver extends BroadcastReceiver { 26 | 27 | @Override 28 | public void onReceive(Context context, Intent intent) { 29 | Intent serviceIntent = new Intent(context, ForegroundService.class); 30 | context.startService(serviceIntent); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/at/andreasrohner/spartantimelapserec/ServiceHelper.java: -------------------------------------------------------------------------------- 1 | package at.andreasrohner.spartantimelapserec; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Build; 6 | import android.widget.Toast; 7 | 8 | /** 9 | * Helper class to start / stop picture service 10 | */ 11 | public class ServiceHelper { 12 | 13 | /** 14 | * Context 15 | */ 16 | private final Context context; 17 | 18 | /** 19 | * Constructor 20 | * 21 | * @param context Context 22 | */ 23 | public ServiceHelper(Context context) { 24 | this.context = context; 25 | } 26 | 27 | /** 28 | * Start 29 | * 30 | * @param calledFromUi true if called from Activity, false if not 31 | */ 32 | public void start(boolean calledFromUi) { 33 | Intent intent = new Intent(context, ForegroundService.class); 34 | if (ForegroundService.mIsRunning) { 35 | if (calledFromUi) { 36 | Toast.makeText(context, context.getString(R.string.error_already_running), Toast.LENGTH_SHORT).show(); 37 | } 38 | } else { 39 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 40 | context.startForegroundService(intent); 41 | } else { 42 | context.startService(intent); 43 | } 44 | } 45 | } 46 | 47 | /** 48 | * Stop 49 | */ 50 | public void stop() { 51 | Intent intent = new Intent(context, ForegroundService.class); 52 | intent.setAction(ForegroundService.ACTION_STOP_SERVICE); 53 | context.startService(intent); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/at/andreasrohner/spartantimelapserec/SettingsFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Spartan Time Lapse Recorder - Minimalistic android time lapse recording app 3 | * Copyright (C) 2014 Andreas Rohner 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package at.andreasrohner.spartantimelapserec; 20 | 21 | import android.content.Context; 22 | import android.os.Bundle; 23 | import android.preference.PreferenceFragment; 24 | 25 | public class SettingsFragment extends PreferenceFragment { 26 | SettingsCommon settCommon; 27 | 28 | @Override 29 | public void onCreate(Bundle savedInstanceState) { 30 | Context context = getActivity().getApplicationContext(); 31 | 32 | super.onCreate(savedInstanceState); 33 | 34 | addPreferencesFromResource(R.xml.preferences); 35 | 36 | settCommon = new SettingsCommon(); 37 | settCommon.onCreate(context, getPreferenceScreen()); 38 | } 39 | 40 | @Override 41 | public void onResume() { 42 | super.onResume(); 43 | settCommon.onResume(getPreferenceScreen()); 44 | } 45 | 46 | @Override 47 | public void onPause() { 48 | super.onPause(); 49 | settCommon.onPause(getPreferenceScreen()); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/at/andreasrohner/spartantimelapserec/data/RecMode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Spartan Time Lapse Recorder - Minimalistic android time lapse recording app 3 | * Copyright (C) 2014 Andreas Rohner 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package at.andreasrohner.spartantimelapserec.data; 20 | 21 | public enum RecMode { 22 | VIDEO, VIDEO_TIME_LAPSE, IMAGE_TIME_LAPSE 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/at/andreasrohner/spartantimelapserec/data/RecSettings.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Spartan Time Lapse Recorder - Minimalistic android time lapse recording app 3 | * Copyright (C) 2014 Andreas Rohner 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package at.andreasrohner.spartantimelapserec.data; 20 | 21 | import static android.os.Environment.DIRECTORY_PICTURES; 22 | 23 | 24 | import android.content.Context; 25 | import android.content.SharedPreferences; 26 | import android.media.CamcorderProfile; 27 | import android.os.Environment; 28 | import at.andreasrohner.spartantimelapserec.preference.DateTimePreference; 29 | 30 | public class RecSettings { 31 | private int cameraId; 32 | private String projectName; 33 | private String projectPath; 34 | private RecMode recMode; 35 | private int frameRate; 36 | private int captureRate; 37 | private boolean muteShutter; 38 | private boolean stopOnLowBattery; 39 | private boolean stopOnLowStorage; 40 | private int initDelay; 41 | private int jpegQuality; 42 | private int frameWidth; 43 | private int frameHeight; 44 | private int recProfile; 45 | private long schedRecTime; 46 | private boolean schedRecEnabled; 47 | private int stopRecAfter; 48 | private int exposureCompensation; 49 | private int zoom; 50 | private int cameraInitDelay; 51 | private int cameraTriggerDelay; 52 | private boolean cameraFlash; 53 | private int videoEncodingBitRate; 54 | 55 | public static int getInteger(SharedPreferences prefs, String key, int def) { 56 | try { 57 | return Integer.parseInt(prefs.getString(key, "")); 58 | } catch (NumberFormatException e) {} 59 | return def; 60 | } 61 | 62 | public static RecMode getRecMode(SharedPreferences prefs, String key, 63 | RecMode def) { 64 | try { 65 | return RecMode.valueOf(prefs.getString(key, "")); 66 | } catch (Exception e) {} 67 | return def; 68 | } 69 | 70 | private boolean checkRecProfile(int profile) { 71 | try { 72 | if (CamcorderProfile.hasProfile(cameraId, profile)) { 73 | return true; 74 | } 75 | } catch (Exception e) {} 76 | 77 | return false; 78 | } 79 | 80 | private int selectRecVideoProfile() { 81 | if (checkRecProfile(CamcorderProfile.QUALITY_1080P) 82 | && frameHeight == 1080) 83 | return CamcorderProfile.QUALITY_1080P; 84 | if (checkRecProfile(CamcorderProfile.QUALITY_720P) 85 | && frameHeight == 720) 86 | return CamcorderProfile.QUALITY_720P; 87 | if (checkRecProfile(CamcorderProfile.QUALITY_480P) 88 | && frameHeight == 480) 89 | return CamcorderProfile.QUALITY_480P; 90 | 91 | if (checkRecProfile(CamcorderProfile.QUALITY_HIGH) 92 | && frameHeight >= 480) 93 | return CamcorderProfile.QUALITY_HIGH; 94 | 95 | if (checkRecProfile(CamcorderProfile.QUALITY_QVGA) 96 | && frameHeight == 240) 97 | return CamcorderProfile.QUALITY_QVGA; 98 | 99 | if (checkRecProfile(CamcorderProfile.QUALITY_QCIF) 100 | && frameHeight == 144) 101 | return CamcorderProfile.QUALITY_QCIF; 102 | 103 | if (checkRecProfile(CamcorderProfile.QUALITY_LOW)) 104 | return CamcorderProfile.QUALITY_LOW; 105 | 106 | return CamcorderProfile.QUALITY_HIGH; 107 | } 108 | 109 | private int selectRecVideoTimeLapseProfile() { 110 | if (checkRecProfile(CamcorderProfile.QUALITY_TIME_LAPSE_1080P) 111 | && frameHeight == 1080) 112 | return CamcorderProfile.QUALITY_TIME_LAPSE_1080P; 113 | if (checkRecProfile(CamcorderProfile.QUALITY_TIME_LAPSE_720P) 114 | && frameHeight == 720) 115 | return CamcorderProfile.QUALITY_TIME_LAPSE_720P; 116 | if (checkRecProfile(CamcorderProfile.QUALITY_TIME_LAPSE_480P) 117 | && frameHeight == 480) 118 | return CamcorderProfile.QUALITY_TIME_LAPSE_480P; 119 | 120 | if (checkRecProfile(CamcorderProfile.QUALITY_TIME_LAPSE_HIGH) 121 | && frameHeight >= 480) 122 | return CamcorderProfile.QUALITY_TIME_LAPSE_HIGH; 123 | 124 | if (checkRecProfile(CamcorderProfile.QUALITY_TIME_LAPSE_QVGA) 125 | && frameHeight == 240) 126 | return CamcorderProfile.QUALITY_TIME_LAPSE_QVGA; 127 | 128 | if (checkRecProfile(CamcorderProfile.QUALITY_TIME_LAPSE_QCIF) 129 | && frameHeight == 144) 130 | return CamcorderProfile.QUALITY_TIME_LAPSE_QCIF; 131 | 132 | if (checkRecProfile(CamcorderProfile.QUALITY_TIME_LAPSE_LOW)) 133 | return CamcorderProfile.QUALITY_TIME_LAPSE_LOW; 134 | 135 | return CamcorderProfile.QUALITY_TIME_LAPSE_HIGH; 136 | } 137 | 138 | public void load(Context context, SharedPreferences prefs) { 139 | videoEncodingBitRate = getInteger(prefs,"pref_video_encoding_br", 0); 140 | cameraId = getInteger(prefs, "pref_camera", 0); 141 | projectName = prefs.getString("pref_project_title", ""); 142 | projectPath = Environment.getExternalStoragePublicDirectory(DIRECTORY_PICTURES).getPath(); 143 | 144 | recMode = getRecMode(prefs, "pref_rec_mode", RecMode.VIDEO_TIME_LAPSE); 145 | 146 | frameRate = getInteger(prefs, "pref_frame_rate", 30); 147 | captureRate = prefs.getInt("pref_capture_rate", 1000); 148 | muteShutter = prefs.getBoolean("pref_mute_shutter", true); 149 | stopOnLowBattery = prefs.getBoolean("pref_stop_low_battery", true); 150 | stopOnLowStorage = prefs.getBoolean("pref_stop_low_storage", true); 151 | initDelay = prefs.getInt("pref_initial_delay", 1000); 152 | jpegQuality = prefs.getInt("pref_jpeg_quality", 90); 153 | stopRecAfter = prefs.getInt("pref_stop_recording_after", 60 * 48); //two days 154 | exposureCompensation = prefs.getInt("pref_exposurecomp",0); 155 | zoom = prefs.getInt("pref_zoom",0); 156 | cameraInitDelay = prefs.getInt("pref_camera_init_delay", 500); 157 | cameraTriggerDelay = prefs.getInt("pref_camera_trigger_delay", 1000); 158 | cameraFlash = prefs.getBoolean("pref_flash",false); 159 | // negative value disables the limit 160 | if (stopRecAfter >= 47 * 60) 161 | stopRecAfter = -1; 162 | else 163 | stopRecAfter *= 60 * 1000; //convert to milli seconds 164 | 165 | String[] size = prefs.getString("pref_frame_size", "1920x1080").split("x"); 166 | try { 167 | frameWidth = Integer.valueOf(size[0]); 168 | frameHeight = Integer.valueOf(size[1]); 169 | } catch (NumberFormatException e) { 170 | frameWidth = 1920; 171 | frameHeight = 1080; 172 | } 173 | 174 | if (recMode != RecMode.IMAGE_TIME_LAPSE) { 175 | if (recMode == RecMode.VIDEO_TIME_LAPSE) 176 | recProfile = selectRecVideoTimeLapseProfile(); 177 | else 178 | recProfile = selectRecVideoProfile(); 179 | } 180 | 181 | String schedRecValue = prefs.getString("pref_schedule_recording", null); 182 | if (schedRecValue != null) { 183 | schedRecEnabled = DateTimePreference.parseEnabled(schedRecValue); 184 | if (schedRecEnabled) { 185 | schedRecTime = DateTimePreference.parseTime(schedRecValue); 186 | } 187 | } else { 188 | schedRecEnabled = false; 189 | } 190 | 191 | } 192 | 193 | public boolean shouldUsePowerSaveMode() { 194 | if (captureRate > 10 * 1000) 195 | return true; 196 | return false; 197 | } 198 | 199 | public int getInitDelay() { 200 | return initDelay; 201 | } 202 | 203 | public void setInitDelay(int initDelay) { 204 | this.initDelay = initDelay; 205 | } 206 | 207 | public int getCameraId() { 208 | return cameraId; 209 | } 210 | 211 | public void setCameraId(int cameraId) { 212 | this.cameraId = cameraId; 213 | } 214 | 215 | public RecMode getRecMode() { 216 | return recMode; 217 | } 218 | 219 | public void setRecMode(RecMode recMode) { 220 | this.recMode = recMode; 221 | } 222 | 223 | public String getProjectName() { 224 | return projectName; 225 | } 226 | 227 | public void setProjectName(String projectName) { 228 | this.projectName = projectName; 229 | } 230 | 231 | public String getProjectPath() { 232 | return projectPath; 233 | } 234 | 235 | public void setProjectPath(String projectPath) { 236 | this.projectPath = projectPath; 237 | } 238 | 239 | public int getFrameRate() { 240 | return frameRate; 241 | } 242 | 243 | public void setFrameRate(int frameRate) { 244 | this.frameRate = frameRate; 245 | } 246 | 247 | public int getCaptureRate() { 248 | return captureRate; 249 | } 250 | 251 | public void setCaptureRate(int captureRate) { 252 | this.captureRate = captureRate; 253 | } 254 | 255 | public boolean isMuteShutter() { 256 | return muteShutter; 257 | } 258 | 259 | public void setMuteShutter(boolean muteShutter) { 260 | this.muteShutter = muteShutter; 261 | } 262 | 263 | public int getJpegQuality() { 264 | return jpegQuality; 265 | } 266 | 267 | public void setJpegQuality(int jpegQuality) { 268 | this.jpegQuality = jpegQuality; 269 | } 270 | 271 | public int getFrameWidth() { 272 | return frameWidth; 273 | } 274 | 275 | public void setFrameWidth(int frameWidth) { 276 | this.frameWidth = frameWidth; 277 | } 278 | 279 | public int getFrameHeight() { 280 | return frameHeight; 281 | } 282 | 283 | public void setFrameHeight(int frameHeight) { 284 | this.frameHeight = frameHeight; 285 | } 286 | 287 | public int getRecProfile() { 288 | return recProfile; 289 | } 290 | 291 | public void setRecProfile(int recProfile) { 292 | this.recProfile = recProfile; 293 | } 294 | 295 | public boolean isStopOnLowBattery() { 296 | return stopOnLowBattery; 297 | } 298 | 299 | public void setStopOnLowBattery(boolean stopOnLowBattery) { 300 | this.stopOnLowBattery = stopOnLowBattery; 301 | } 302 | 303 | public boolean isStopOnLowStorage() { 304 | return stopOnLowStorage; 305 | } 306 | 307 | public void setStopOnLowStorage(boolean stopOnLowStorage) { 308 | this.stopOnLowStorage = stopOnLowStorage; 309 | } 310 | 311 | public long getSchedRecTime() { 312 | return schedRecTime; 313 | } 314 | 315 | public void setSchedRecTime(long schedRecTime) { 316 | this.schedRecTime = schedRecTime; 317 | } 318 | 319 | public boolean isSchedRecEnabled() { 320 | return schedRecEnabled; 321 | } 322 | 323 | public void setSchedRecEnabled(boolean schedRecEnabled) { 324 | this.schedRecEnabled = schedRecEnabled; 325 | } 326 | 327 | public int getStopRecAfter() { 328 | return stopRecAfter; 329 | } 330 | 331 | public int getExposureCompensation() { return exposureCompensation; } 332 | 333 | public int getZoom() { return zoom;} 334 | 335 | public void setStopRecAfter(int stopRecAfter) { 336 | this.stopRecAfter = stopRecAfter; 337 | } 338 | 339 | public int getCameraInitDelay() {return cameraInitDelay;} 340 | 341 | public int getCameraTriggerDelay() {return cameraTriggerDelay;} 342 | 343 | public boolean getCameraFlash() {return cameraFlash;} 344 | 345 | public int getVideoEncodingBitRate() {return videoEncodingBitRate;} 346 | } 347 | -------------------------------------------------------------------------------- /app/src/main/java/at/andreasrohner/spartantimelapserec/preference/DateTimePreference.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Spartan Time Lapse Recorder - Minimalistic android time lapse recording app 3 | * Copyright (C) 2014 Andreas Rohner 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package at.andreasrohner.spartantimelapserec.preference; 20 | 21 | import java.text.DateFormat; 22 | import java.text.SimpleDateFormat; 23 | import java.util.Calendar; 24 | 25 | import android.content.Context; 26 | import android.content.res.TypedArray; 27 | import android.preference.DialogPreference; 28 | import android.util.AttributeSet; 29 | import android.view.View; 30 | import android.widget.CheckBox; 31 | import android.widget.CompoundButton; 32 | import android.widget.CompoundButton.OnCheckedChangeListener; 33 | import android.widget.DatePicker; 34 | import android.widget.TimePicker; 35 | import at.andreasrohner.spartantimelapserec.R; 36 | 37 | public class DateTimePreference extends DialogPreference implements 38 | OnCheckedChangeListener { 39 | Calendar mCal; 40 | boolean mEnabled; 41 | DatePicker mDatePicker; 42 | TimePicker mTimePicker; 43 | 44 | public DateTimePreference(Context context, AttributeSet attrs) { 45 | super(context, attrs); 46 | 47 | mCal = Calendar.getInstance(); 48 | 49 | setWidgetLayoutResource(R.layout.dialog_date_widget_preference); 50 | setDialogLayoutResource(R.layout.dialog_date_preference); 51 | } 52 | 53 | public long getTimeInMillis() { 54 | if (mDatePicker != null && mTimePicker != null) 55 | mCal.set(mDatePicker.getYear(), mDatePicker.getMonth(), 56 | mDatePicker.getDayOfMonth(), mTimePicker.getCurrentHour(), 57 | mTimePicker.getCurrentMinute()); 58 | 59 | if (mCal.getTimeInMillis() < System.currentTimeMillis()) 60 | mCal.setTimeInMillis(System.currentTimeMillis()); 61 | 62 | mCal.set(Calendar.SECOND, 0); 63 | 64 | return mCal.getTimeInMillis(); 65 | } 66 | 67 | public static long parseTime(String value) { 68 | long time = System.currentTimeMillis(); 69 | try { 70 | long temp; 71 | if (value != null) { 72 | String[] parts = value.split("\\|"); 73 | if (parts.length == 2) { 74 | temp = Long.valueOf(parts[1]); 75 | if (temp > time) 76 | time = temp; 77 | } 78 | } 79 | } catch (NumberFormatException e) { 80 | } 81 | 82 | return time; 83 | } 84 | 85 | public static boolean parseEnabled(String value) { 86 | if (value != null && value.charAt(0) == '1') 87 | return true; 88 | 89 | return false; 90 | } 91 | 92 | public String formatDateTime() { 93 | DateFormat f = SimpleDateFormat.getDateTimeInstance( 94 | SimpleDateFormat.MEDIUM, SimpleDateFormat.SHORT); 95 | 96 | return f.format(mCal.getTime()); 97 | } 98 | 99 | private void parseValue(String value) { 100 | mCal.setTimeInMillis(parseTime(value)); 101 | mCal.set(Calendar.SECOND, 0); 102 | } 103 | 104 | private String createValue() { 105 | String time = (mEnabled ? "1|" : "0|"); 106 | time += String.valueOf(getTimeInMillis()); 107 | 108 | return time; 109 | } 110 | 111 | @Override 112 | protected void onBindView(View view) { 113 | super.onBindView(view); 114 | 115 | CheckBox checkBox = (CheckBox) view 116 | .findViewById(R.id.dialog_date_preference_enabled); 117 | 118 | String persisted = getPersistedString(null); 119 | mEnabled = parseEnabled(persisted); 120 | 121 | if (mEnabled) { 122 | if (!(mCal.getTimeInMillis() >= System.currentTimeMillis() + 10000)) mEnabled = false; 123 | } 124 | checkBox.setChecked(mEnabled); 125 | checkBox.setOnCheckedChangeListener(this); 126 | } 127 | 128 | @Override 129 | protected void onBindDialogView(View v) { 130 | super.onBindDialogView(v); 131 | 132 | parseValue(getPersistedString(null)); 133 | 134 | mDatePicker = (DatePicker) v.findViewById(R.id.dialog_date_preference_date); 135 | mTimePicker = (TimePicker) v.findViewById(R.id.dialog_date_preference_time); 136 | 137 | mDatePicker.setCalendarViewShown(false); 138 | mDatePicker.setMinDate(System.currentTimeMillis() - 1000); 139 | 140 | mDatePicker.updateDate(mCal.get(Calendar.YEAR), 141 | mCal.get(Calendar.MONTH), mCal.get(Calendar.DAY_OF_MONTH)); 142 | 143 | mTimePicker.setIs24HourView(true); 144 | mTimePicker.setCurrentHour(mCal.get(Calendar.HOUR_OF_DAY)); 145 | mTimePicker.setCurrentMinute(mCal.get(Calendar.MINUTE)); 146 | } 147 | 148 | @Override 149 | protected void onDialogClosed(boolean positiveResult) { 150 | super.onDialogClosed(positiveResult); 151 | 152 | if (positiveResult) { 153 | String time = createValue(); 154 | 155 | if (callChangeListener(time)) { 156 | persistString(time); 157 | } 158 | } 159 | } 160 | 161 | @Override 162 | protected Object onGetDefaultValue(TypedArray a, int index) { 163 | return a.getString(index); 164 | } 165 | 166 | @Override 167 | protected void onSetInitialValue(boolean restoreValue, Object defaultValue) { 168 | String time = null; 169 | 170 | if (restoreValue) { 171 | time = getPersistedString((String) defaultValue); 172 | } else { 173 | time = (String) defaultValue; 174 | } 175 | 176 | parseValue(time); 177 | } 178 | 179 | @Override 180 | public void onCheckedChanged(CompoundButton check, boolean value) { 181 | 182 | mEnabled = value; 183 | if (mEnabled) { 184 | if (!(mCal.getTimeInMillis() >= System.currentTimeMillis())) { 185 | mEnabled = false; 186 | check.setChecked(false); 187 | } 188 | } 189 | 190 | String time = createValue(); 191 | if (callChangeListener(time)) { 192 | persistString(time); 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /app/src/main/java/at/andreasrohner/spartantimelapserec/preference/EditSummaryPreference.java: -------------------------------------------------------------------------------- 1 | package at.andreasrohner.spartantimelapserec.preference; 2 | 3 | import android.content.Context; 4 | import android.preference.EditTextPreference; 5 | import android.util.AttributeSet; 6 | 7 | /** 8 | * Show value in summary 9 | */ 10 | public class EditSummaryPreference extends EditTextPreference { 11 | 12 | public EditSummaryPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 13 | super(context, attrs, defStyleAttr, defStyleRes); 14 | } 15 | 16 | public EditSummaryPreference(Context context, AttributeSet attrs, int defStyleAttr) { 17 | super(context, attrs, defStyleAttr); 18 | } 19 | 20 | public EditSummaryPreference(Context context, AttributeSet attrs) { 21 | super(context, attrs); 22 | } 23 | 24 | public EditSummaryPreference(Context context) { 25 | super(context); 26 | } 27 | 28 | @Override 29 | public CharSequence getSummary() { 30 | return getText(); 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/at/andreasrohner/spartantimelapserec/preference/IntervalPickerPreference.java: -------------------------------------------------------------------------------- 1 | package at.andreasrohner.spartantimelapserec.preference; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.preference.DialogPreference; 6 | import android.util.AttributeSet; 7 | import android.view.View; 8 | import android.widget.NumberPicker; 9 | 10 | import at.andreasrohner.spartantimelapserec.R; 11 | 12 | public class IntervalPickerPreference extends DialogPreference { 13 | private static final int DEFAULT_VALUE = 0; 14 | 15 | private int mValue = DEFAULT_VALUE; 16 | private NumberPicker mMilliPicker; 17 | private NumberPicker mSecondsPicker; 18 | private NumberPicker mMinutesPicker; 19 | 20 | public IntervalPickerPreference(Context context, AttributeSet attrs) { 21 | 22 | super(context, attrs); 23 | 24 | setDialogLayoutResource(R.layout.dialog_intervalpicker_preference); 25 | } 26 | 27 | 28 | 29 | @Override 30 | protected void onBindDialogView(View v) { 31 | super.onBindDialogView(v); 32 | 33 | mValue = getPersistedInt(DEFAULT_VALUE); 34 | 35 | mMilliPicker = (NumberPicker) v.findViewById(R.id.dialog_seekbar_preference_milli); 36 | String[] nums = new String[100]; 37 | for(int i=0; i. 17 | */ 18 | 19 | package at.andreasrohner.spartantimelapserec.preference; 20 | 21 | import android.content.Context; 22 | import android.content.DialogInterface; 23 | import android.preference.EditTextPreference; 24 | import android.util.AttributeSet; 25 | import android.view.View; 26 | import android.view.inputmethod.InputMethodManager; 27 | 28 | public class NoKBEditTextPreference extends EditTextPreference { 29 | private View mView; 30 | 31 | @Override 32 | protected void onBindView(View view) { 33 | mView = view; 34 | super.onBindView(view); 35 | } 36 | 37 | public NoKBEditTextPreference(Context context) { 38 | super(context); 39 | } 40 | 41 | public NoKBEditTextPreference(Context context, AttributeSet attrs, 42 | int defStyle) { 43 | super(context, attrs, defStyle); 44 | } 45 | 46 | public NoKBEditTextPreference(Context context, AttributeSet attrs) { 47 | super(context, attrs); 48 | } 49 | 50 | @Override 51 | public void onDismiss(DialogInterface dialog) { 52 | super.onDismiss(dialog); 53 | 54 | InputMethodManager imm = (InputMethodManager) getContext() 55 | .getSystemService(Context.INPUT_METHOD_SERVICE); 56 | imm.hideSoftInputFromWindow(mView.getWindowToken(), 57 | InputMethodManager.HIDE_NOT_ALWAYS); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/at/andreasrohner/spartantimelapserec/preference/SeekBarPreference.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Spartan Time Lapse Recorder - Minimalistic android time lapse recording app 3 | * Copyright (C) 2014 Andreas Rohner 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package at.andreasrohner.spartantimelapserec.preference; 20 | 21 | import android.content.Context; 22 | import android.content.res.TypedArray; 23 | import android.preference.DialogPreference; 24 | import android.util.AttributeSet; 25 | import android.view.View; 26 | import android.widget.SeekBar; 27 | import android.widget.TextView; 28 | import at.andreasrohner.spartantimelapserec.R; 29 | 30 | public class SeekBarPreference extends DialogPreference implements 31 | SeekBar.OnSeekBarChangeListener { 32 | private static final int DEFAULT_VALUE = 0; 33 | 34 | private TextView mValueDisp; 35 | private SeekBar mSeekBar; 36 | private String mSuffix; 37 | private int mMaxValue, mMinValue, mValue = DEFAULT_VALUE; 38 | private OnFormatOutputValueListener mFormatListener; 39 | private float mLog; 40 | private int mStickyValue; 41 | 42 | public SeekBarPreference(Context context, AttributeSet attrs) { 43 | 44 | super(context, attrs); 45 | 46 | TypedArray a = context.obtainStyledAttributes(attrs, 47 | R.styleable.TimeLapse); 48 | 49 | int resId = a.getResourceId(R.styleable.TimeLapse_suffix,0); 50 | if (resId == 0) 51 | mSuffix = a.getString(R.styleable.TimeLapse_suffix); 52 | else 53 | mSuffix = context.getString(resId); 54 | 55 | mMaxValue = a.getInteger(R.styleable.TimeLapse_max, 100); 56 | mMinValue = a.getInteger(R.styleable.TimeLapse_min, 0); 57 | mLog = a.getFloat(R.styleable.TimeLapse_log, 0); 58 | mStickyValue = a.getInteger(R.styleable.TimeLapse_stickyValue, 0); 59 | a.recycle(); 60 | setDialogLayoutResource(R.layout.dialog_seekbar_preference); 61 | } 62 | 63 | public interface OnFormatOutputValueListener { 64 | String onFormatOutputValue(int value, String suffix); 65 | } 66 | 67 | public void setOnFormatOutputValueListener( 68 | OnFormatOutputValueListener listener) { 69 | mFormatListener = listener; 70 | } 71 | 72 | public void setMaxValue(int value) { 73 | mMaxValue = value; 74 | if (mSeekBar != null) 75 | mSeekBar.setMax(mMaxValue - mMinValue); 76 | } 77 | 78 | public void setMinValue(int value) { 79 | mMinValue = value; 80 | } 81 | 82 | private int getLogValue(int val) { 83 | if (mLog > 1) 84 | return (int) Math.round(((Math.log(val) / Math.log(mLog)) * 1000)); 85 | return val; 86 | } 87 | 88 | private int getPowValue(int val) { 89 | if (mLog > 1) { 90 | val = (int) Math.round(Math.pow(mLog, ((double) val) / 1000)); 91 | if (mStickyValue > 0) { 92 | int mod = val % mStickyValue; 93 | int area = 15 * mStickyValue / 100; 94 | if (val > mStickyValue) { 95 | if (mod < area || val > mStickyValue * 8) 96 | val -= mod; 97 | else if (mod > mStickyValue - area) 98 | val = val - mod + mStickyValue; 99 | } 100 | } 101 | } 102 | return val; 103 | } 104 | 105 | @Override 106 | protected void onBindDialogView(View v) { 107 | super.onBindDialogView(v); 108 | 109 | mValue = getPersistedInt(DEFAULT_VALUE); 110 | 111 | mValueDisp = (TextView) v 112 | .findViewById(R.id.dialog_seekbar_preference_value); 113 | 114 | mSeekBar = (SeekBar) v 115 | .findViewById(R.id.dialog_seekbar_preference_control); 116 | mSeekBar.setMax(getLogValue(mMaxValue - mMinValue)); 117 | mSeekBar.setOnSeekBarChangeListener(this); 118 | mSeekBar.setProgress(getLogValue(mValue - mMinValue)); 119 | mValueDisp.setText(formatValue(mValue)); 120 | } 121 | 122 | @Override 123 | protected void onDialogClosed(boolean positiveResult) { 124 | if (positiveResult) { 125 | persistInt(mValue); 126 | } 127 | } 128 | 129 | @Override 130 | protected void onSetInitialValue(boolean restorePersistedValue, 131 | Object defaultValue) { 132 | if (restorePersistedValue) { 133 | // Restore existing state 134 | mValue = getPersistedInt(DEFAULT_VALUE); 135 | } else { 136 | // Set default state from the XML attribute 137 | mValue = (Integer) defaultValue; 138 | persistInt(mValue); 139 | } 140 | } 141 | 142 | @Override 143 | protected Object onGetDefaultValue(TypedArray a, int index) { 144 | return a.getInteger(index, DEFAULT_VALUE); 145 | } 146 | 147 | @Override 148 | public void onProgressChanged(SeekBar seek, int value, boolean fromTouch) { 149 | value = getPowValue(value); 150 | value += mMinValue; 151 | mValue = value; 152 | 153 | String text = formatValue(value); 154 | if (mValueDisp != null) 155 | mValueDisp.setText(text); 156 | } 157 | 158 | private String formatValue(int value) { 159 | String text; 160 | if (mFormatListener != null) { 161 | text = mFormatListener.onFormatOutputValue(value, mSuffix); 162 | } else { 163 | text = String.valueOf(value); 164 | if (mSuffix != null) 165 | text += " " + mSuffix; 166 | } 167 | return text; 168 | } 169 | 170 | public int getmValue(){return mValue;} 171 | 172 | @Override 173 | public void onStartTrackingTouch(SeekBar arg0) { 174 | } 175 | 176 | @Override 177 | public void onStopTrackingTouch(SeekBar arg0) { 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /app/src/main/java/at/andreasrohner/spartantimelapserec/recorder/ImageRecorder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Spartan Time Lapse Recorder - Minimalistic android time lapse recording app 3 | * Copyright (C) 2014 Andreas Rohner 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package at.andreasrohner.spartantimelapserec.recorder; 20 | 21 | import java.io.File; 22 | import java.io.FileOutputStream; 23 | import java.io.IOException; 24 | import java.util.HashSet; 25 | import java.util.List; 26 | import java.util.Set; 27 | 28 | import android.content.Context; 29 | import android.graphics.ImageFormat; 30 | import android.graphics.SurfaceTexture; 31 | import android.hardware.Camera; 32 | import android.hardware.Camera.AutoFocusCallback; 33 | import android.hardware.Camera.ErrorCallback; 34 | import android.os.Handler; 35 | import android.os.Looper; 36 | import android.os.SystemClock; 37 | import android.util.Log; 38 | 39 | import at.andreasrohner.spartantimelapserec.data.RecSettings; 40 | 41 | public class ImageRecorder extends Recorder implements Runnable, 42 | Camera.PictureCallback, ErrorCallback, AutoFocusCallback { 43 | private static final int CONTINUOUS_CAPTURE_THRESHOLD = 3000; 44 | private static final int RELEASE_CAMERA_THRESHOLD = 2000; 45 | protected long mEndTime; 46 | protected long mStartPreviewTime; 47 | protected boolean mUseAutoFocus; 48 | protected Camera.PictureCallback pictureCallback; 49 | protected AutoFocusCallback autoFocusCallback; 50 | protected boolean mWaitCamReady; 51 | 52 | /** 53 | * Current / last recorded image 54 | */ 55 | private static File currentRecordedImage; 56 | 57 | /** 58 | * Count of recorded images within the whole app session 59 | */ 60 | private static int recordedImagesCount = 0; 61 | 62 | public ImageRecorder(RecSettings settings, 63 | Context context, Handler handler) { 64 | super(settings, context, handler); 65 | 66 | if (settings.getStopRecAfter() > 0) 67 | mEndTime = System.currentTimeMillis() + settings.getInitDelay() 68 | + settings.getStopRecAfter(); 69 | 70 | if (mCanDisableShutterSound) 71 | mMute = null; 72 | 73 | pictureCallback=this; 74 | autoFocusCallback=this; 75 | } 76 | 77 | /** 78 | * @return Current / last recorded image 79 | */ 80 | public static File getCurrentRecordedImage() { 81 | return currentRecordedImage; 82 | } 83 | 84 | /** 85 | * @return Count of recorded images within the whole app session 86 | */ 87 | public static int getRecordedImagesCount() { 88 | return recordedImagesCount; 89 | } 90 | 91 | @Override 92 | public void stop() { 93 | if (mHandler != null) 94 | mHandler.removeCallbacks(this); 95 | 96 | muteShutter(); 97 | 98 | super.stop(); 99 | } 100 | 101 | protected void scheduleNextPicture() { 102 | long diffTime = SystemClock.elapsedRealtime() - mStartPreviewTime; 103 | long delay = mSettings.getCaptureRate() - diffTime; 104 | if (delay >= RELEASE_CAMERA_THRESHOLD && mSettings.getCaptureRate() >= CONTINUOUS_CAPTURE_THRESHOLD){ 105 | releaseCamera(); 106 | } 107 | 108 | if (delay <= 0) 109 | mHandler.post(this); 110 | else 111 | mHandler.postDelayed(this, delay); 112 | } 113 | 114 | @Override 115 | public void onPictureTaken(byte[] data, Camera camera) { 116 | try { 117 | File file = getOutputFile("jpg"); 118 | currentRecordedImage = file; 119 | FileOutputStream out = new FileOutputStream(file); 120 | out.write(data); 121 | out.close(); 122 | mWaitCamReady = false; 123 | recordedImagesCount++; 124 | scheduleNextPicture(); 125 | } catch (Exception e) { 126 | handleError(getClass().getSimpleName(), e.getMessage()); 127 | } 128 | } 129 | 130 | @Override 131 | public void onAutoFocus(boolean success, Camera camera) { 132 | try { 133 | muteShutter(); 134 | new Handler(Looper.getMainLooper()).postDelayed(() -> { 135 | if (mCamera!=null) { 136 | camera.takePicture(null, null, pictureCallback); 137 | } 138 | }, mWaitCamReady ? mSettings.getCameraTriggerDelay() : 0); 139 | 140 | } catch (Exception e) { 141 | e.printStackTrace(); 142 | handleError(getClass().getSimpleName(), e.getMessage()); 143 | } 144 | } 145 | 146 | @Override 147 | public void run() { 148 | try { 149 | if (mEndTime > 0 && mEndTime < System.currentTimeMillis()) { 150 | success(); //tell service to stop 151 | return; 152 | } 153 | 154 | mStartPreviewTime = SystemClock.elapsedRealtime(); 155 | 156 | if (mCamera == null) 157 | prepareRecord(); 158 | 159 | Log.d("Camera","Wait:"+ mWaitCamReady); 160 | 161 | new Handler(Looper.getMainLooper()).postDelayed(() -> { 162 | if (mCamera!=null) { 163 | mCamera.startPreview(); 164 | if (mUseAutoFocus) { 165 | mCamera.autoFocus(autoFocusCallback); 166 | } 167 | else { 168 | onAutoFocus(true, mCamera); 169 | } 170 | } 171 | }, mWaitCamReady ? mSettings.getCameraInitDelay() : 0); 172 | 173 | } catch (Exception e) { 174 | Log.e("Error","startPreview"); 175 | e.printStackTrace(); 176 | handleError(getClass().getSimpleName(), e.getMessage()); 177 | } 178 | } 179 | 180 | protected void setWhiteBalance(Camera.Parameters params, 181 | Set suppModes) { 182 | if (suppModes.contains(Camera.Parameters.WHITE_BALANCE_AUTO)) { 183 | params.setWhiteBalance(Camera.Parameters.WHITE_BALANCE_AUTO); 184 | } 185 | } 186 | 187 | protected void setFocusMode(Camera.Parameters params, Set suppModes) { 188 | if (mSettings.getCaptureRate() < CONTINUOUS_CAPTURE_THRESHOLD && suppModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { 189 | params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); 190 | } else if (suppModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO) 191 | && mSettings.getCaptureRate() < CONTINUOUS_CAPTURE_THRESHOLD) { 192 | params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO); 193 | } else if (suppModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) { 194 | params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); 195 | mUseAutoFocus = true; 196 | } 197 | } 198 | 199 | protected void setCameraParams() throws IOException { 200 | Camera.Parameters params = mCamera.getParameters(); 201 | 202 | /* 203 | * params.set("cam_mode", 1); hack is not necessary for pictures 204 | */ 205 | 206 | List suppList = params.getSupportedWhiteBalance(); 207 | if (suppList != null) { 208 | Set suppModes = new HashSet(); 209 | suppModes.addAll(suppList); 210 | 211 | setWhiteBalance(params, suppModes); 212 | } 213 | 214 | suppList = params.getSupportedFocusModes(); 215 | if (suppList != null) { 216 | Set suppModes = new HashSet(); 217 | suppModes.addAll(suppList); 218 | 219 | setFocusMode(params, suppModes); 220 | } 221 | 222 | params.setPictureFormat(ImageFormat.JPEG); 223 | 224 | params.setPictureSize(mSettings.getFrameWidth(), 225 | mSettings.getFrameHeight()); 226 | 227 | params.setJpegQuality(mSettings.getJpegQuality()); 228 | params.setRotation(getCameraRotation(mSettings.getCameraId())); 229 | params.setExposureCompensation(mSettings.getExposureCompensation()); 230 | params.setZoom(mSettings.getZoom()); 231 | params.setFlashMode(mSettings.getCameraFlash() ? Camera.Parameters.FLASH_MODE_ON : Camera.Parameters.FLASH_MODE_OFF); 232 | mCamera.setParameters(params); 233 | 234 | mCamera.setErrorCallback(this); 235 | } 236 | 237 | protected void prepareRecord() throws IOException { 238 | mWaitCamReady = mCamera == null; 239 | releaseCamera(); 240 | 241 | mCamera = Camera.open(mSettings.getCameraId()); 242 | 243 | setCameraParams(); 244 | 245 | SurfaceTexture surfaceTexture = new SurfaceTexture(10); 246 | mCamera.setPreviewTexture(surfaceTexture); 247 | 248 | } 249 | 250 | protected void doRecord() { 251 | run(); 252 | } 253 | 254 | @Override 255 | public void onError(int error, Camera camera) { 256 | switch (error) { 257 | case Camera.CAMERA_ERROR_SERVER_DIED: 258 | handleError(getClass().getSimpleName(), "Cameraserver died"); 259 | break; 260 | default: 261 | handleError(getClass().getSimpleName(), 262 | "Unkown error occured while recording"); 263 | break; 264 | } 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /app/src/main/java/at/andreasrohner/spartantimelapserec/recorder/PowerSavingImageRecorder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Spartan Time Lapse Recorder - Minimalistic android time lapse recording app 3 | * Copyright (C) 2014 Andreas Rohner 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package at.andreasrohner.spartantimelapserec.recorder; 20 | 21 | import android.app.AlarmManager; 22 | import android.app.PendingIntent; 23 | import android.content.Context; 24 | import android.content.Intent; 25 | import android.os.Handler; 26 | import android.os.PowerManager.WakeLock; 27 | import android.os.SystemClock; 28 | import at.andreasrohner.spartantimelapserec.MainActivity; 29 | import at.andreasrohner.spartantimelapserec.PowerSavingReceiver; 30 | import at.andreasrohner.spartantimelapserec.data.RecSettings; 31 | 32 | public class PowerSavingImageRecorder extends ImageRecorder { 33 | private WakeLock mWakeLock; 34 | private AlarmManager mAlarmMgr; 35 | 36 | public PowerSavingImageRecorder(RecSettings settings, 37 | Context context, Handler handler, 38 | WakeLock wakeLock) { 39 | super(settings, context, handler); 40 | 41 | this.mWakeLock = wakeLock; 42 | 43 | mAlarmMgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); 44 | } 45 | 46 | @Override 47 | public void stop() { 48 | if (mContext != null && mAlarmMgr != null) { 49 | Intent intent = new Intent(mContext, PowerSavingReceiver.class); 50 | PendingIntent alarmIntent = PendingIntent.getBroadcast(mContext, 0, 51 | intent, PendingIntent.FLAG_IMMUTABLE); 52 | mAlarmMgr.cancel(alarmIntent); 53 | mAlarmMgr = null; 54 | } 55 | 56 | super.stop(); 57 | } 58 | 59 | @Override 60 | protected void scheduleNextPicture() { 61 | if (mContext != null && mAlarmMgr != null) { 62 | Intent intent = new Intent(mContext, PowerSavingReceiver.class); 63 | PendingIntent alarmIntent = PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE); 64 | 65 | Intent intent2 = new Intent(mContext, MainActivity.class); 66 | intent2.setAction(Intent.ACTION_MAIN); 67 | intent2.addCategory(Intent.CATEGORY_LAUNCHER); 68 | PendingIntent openIntent = PendingIntent.getActivity(mContext, 0, intent2, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); 69 | 70 | long diffTime = SystemClock.elapsedRealtime() - mStartPreviewTime; 71 | long delay = mSettings.getCaptureRate() - diffTime; 72 | if (delay < 0) 73 | delay = 0; 74 | 75 | mAlarmMgr.setAlarmClock(new AlarmManager.AlarmClockInfo(System.currentTimeMillis() + delay, openIntent), alarmIntent); 76 | } 77 | 78 | disableOrientationSensor(); 79 | releaseCamera(); 80 | unmuteShutter(); 81 | 82 | mWakeLock.release(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /app/src/main/java/at/andreasrohner/spartantimelapserec/recorder/Recorder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Spartan Time Lapse Recorder - Minimalistic android time lapse recording app 3 | * Copyright (C) 2014 Andreas Rohner 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package at.andreasrohner.spartantimelapserec.recorder; 20 | 21 | import java.io.File; 22 | import java.io.IOException; 23 | 24 | import android.content.Context; 25 | import android.hardware.Camera; 26 | import android.hardware.Camera.CameraInfo; 27 | import android.os.Bundle; 28 | import android.os.Handler; 29 | import android.os.Message; 30 | import android.os.SystemClock; 31 | import android.os.PowerManager.WakeLock; 32 | import android.text.format.DateFormat; 33 | import android.util.Log; 34 | import at.andreasrohner.spartantimelapserec.data.RecSettings; 35 | import at.andreasrohner.spartantimelapserec.sensor.MuteShutter; 36 | import at.andreasrohner.spartantimelapserec.sensor.OrientationSensor; 37 | 38 | public abstract class Recorder { 39 | protected Context mContext; 40 | protected RecSettings mSettings; 41 | protected Camera mCamera; 42 | protected boolean mCanDisableShutterSound; 43 | protected Handler mHandler; 44 | protected int mInitDelay; 45 | private OrientationSensor mOrientation; 46 | protected MuteShutter mMute; 47 | private File mOutputDir; 48 | private int mFileIndex; 49 | 50 | public static Recorder getInstance(RecSettings settings, 51 | Context context, Handler handler, 52 | WakeLock wakeLock) { 53 | Recorder recorder; 54 | 55 | switch (settings.getRecMode()) { 56 | case VIDEO_TIME_LAPSE: 57 | recorder = new VideoTimeLapseRecorder(settings, 58 | context, handler); 59 | break; 60 | case IMAGE_TIME_LAPSE: 61 | if (settings.shouldUsePowerSaveMode()) { 62 | recorder = new PowerSavingImageRecorder(settings, 63 | context, handler, wakeLock); 64 | } else { 65 | recorder = new ImageRecorder(settings, context, 66 | handler); 67 | } 68 | break; 69 | default: 70 | recorder = new VideoRecorder(settings, context, 71 | handler); 72 | break; 73 | } 74 | 75 | return recorder; 76 | } 77 | 78 | public Recorder(RecSettings settings, Context context, Handler handler) { 79 | mContext = context; 80 | 81 | mOrientation = new OrientationSensor(context); 82 | mOrientation.enable(); 83 | 84 | mSettings = settings; 85 | mHandler = handler; 86 | 87 | CameraInfo info = new CameraInfo(); 88 | Camera.getCameraInfo(mSettings.getCameraId(), info); 89 | mCanDisableShutterSound = info.canDisableShutterSound; 90 | 91 | mMute = new MuteShutter(context); 92 | mOutputDir = new File(settings.getProjectPath() + "/" 93 | + settings.getProjectName() + "/" 94 | + DateFormat.format("yyyy-MM-dd", System.currentTimeMillis()) 95 | + "/"); 96 | 97 | if (!mOutputDir.exists() && !mOutputDir.mkdirs()) { 98 | Log.e("TimeLapseCamera", "Failed to make directory: " + mOutputDir.toString()); 99 | return; 100 | } 101 | 102 | mInitDelay = settings.getInitDelay(); 103 | } 104 | 105 | protected void releaseCamera() { 106 | if (mCamera == null) 107 | return; 108 | 109 | try { 110 | mCamera.reconnect(); 111 | mCamera.release(); 112 | } catch (Exception e) { 113 | e.printStackTrace(); 114 | } 115 | mCamera = null; 116 | } 117 | 118 | protected void handleError(String tag, String msg) { 119 | if (mHandler != null) { 120 | Message m = new Message(); 121 | Bundle b = new Bundle(); 122 | b.putString("status", "error"); 123 | b.putString("tag", tag); 124 | b.putString("msg", msg); 125 | m.setData(b); 126 | m.setTarget(mHandler); 127 | mHandler.sendMessage(m); 128 | mHandler = null; 129 | } 130 | } 131 | 132 | protected void success() { 133 | if (mHandler != null) { 134 | Message m = new Message(); 135 | Bundle b = new Bundle(); 136 | b.putString("status", "success"); 137 | m.setData(b); 138 | m.setTarget(mHandler); 139 | mHandler.sendMessage(m); 140 | } 141 | } 142 | 143 | protected void disableOrientationSensor() { 144 | if (mOrientation != null) 145 | mOrientation.disable(); 146 | } 147 | 148 | protected void enableOrientationSensor() { 149 | if (mOrientation != null) 150 | mOrientation.enable(); 151 | } 152 | 153 | public void stop() { 154 | disableOrientationSensor(); 155 | mOrientation = null; 156 | 157 | releaseCamera(); 158 | unmuteShutter(); 159 | 160 | mHandler = null; 161 | mContext = null; 162 | mMute = null; 163 | } 164 | 165 | protected abstract void prepareRecord() throws Exception; 166 | 167 | protected abstract void doRecord() throws Exception; 168 | 169 | public void start() { 170 | long timeDiff = SystemClock.elapsedRealtime(); 171 | Runnable r = new Runnable() { 172 | @Override 173 | public void run() { 174 | try { 175 | doRecord(); 176 | } catch (Exception e) { 177 | e.printStackTrace(); 178 | handleError(Recorder.class.getSimpleName(), e.getMessage()); 179 | } 180 | } 181 | }; 182 | 183 | if (mHandler == null || mContext == null) 184 | return; 185 | 186 | enableOrientationSensor(); 187 | 188 | try { 189 | prepareRecord(); 190 | } catch (Exception e) { 191 | e.printStackTrace(); 192 | handleError(getClass().getSimpleName(), e.getMessage()); 193 | return; 194 | } 195 | 196 | timeDiff = SystemClock.elapsedRealtime() - timeDiff; 197 | timeDiff = mInitDelay - timeDiff; 198 | if (timeDiff <= 0) 199 | mHandler.post(r); 200 | else 201 | mHandler.postDelayed(r, timeDiff); 202 | 203 | mInitDelay = 0; 204 | } 205 | 206 | public File getOutputDir() { 207 | return mOutputDir; 208 | } 209 | 210 | protected File getOutputFile(String ext) throws IOException { 211 | File outFile; 212 | do { 213 | outFile = new File(mOutputDir, mSettings.getProjectName() 214 | + mFileIndex + "." + ext); 215 | mFileIndex++; 216 | } while (outFile.isFile()); 217 | 218 | if (!mOutputDir.isDirectory()) 219 | throw new IOException("Could not open directory"); 220 | 221 | return outFile; 222 | } 223 | 224 | protected int getCameraRotation(int cameraId) { 225 | if (mOrientation != null) 226 | return mOrientation.getCameraRotation(cameraId); 227 | return 0; 228 | } 229 | 230 | 231 | protected void muteShutter() { 232 | if (mSettings != null && mSettings.isMuteShutter()) { 233 | if (mCanDisableShutterSound) { 234 | // don't merge with upper if (to prevent elseif-branch if 235 | // mCamera == null) 236 | if (mCamera != null) 237 | mCamera.enableShutterSound(false); 238 | } else if (mMute != null) { 239 | mMute.muteShutter(); 240 | } 241 | } 242 | 243 | } 244 | 245 | protected void unmuteShutter() { 246 | if (mSettings != null && mSettings.isMuteShutter()) { 247 | if (mCanDisableShutterSound) { 248 | // don't merge with upper if (to prevent elseif-branch if 249 | // mCamera == null) 250 | if (mCamera != null) 251 | mCamera.enableShutterSound(true); 252 | } else if (mMute != null) { 253 | mMute.unmuteShutter(); 254 | } 255 | } 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /app/src/main/java/at/andreasrohner/spartantimelapserec/recorder/VideoRecorder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Spartan Time Lapse Recorder - Minimalistic android time lapse recording app 3 | * Copyright (C) 2014 Andreas Rohner 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package at.andreasrohner.spartantimelapserec.recorder; 20 | 21 | import java.io.IOException; 22 | import java.util.HashSet; 23 | import java.util.List; 24 | import java.util.Set; 25 | 26 | import android.content.Context; 27 | import android.graphics.SurfaceTexture; 28 | import android.hardware.Camera; 29 | import android.media.CamcorderProfile; 30 | import android.media.MediaRecorder; 31 | import android.media.MediaRecorder.OnErrorListener; 32 | import android.media.MediaRecorder.OnInfoListener; 33 | import android.os.Handler; 34 | import android.util.Log; 35 | import at.andreasrohner.spartantimelapserec.data.RecSettings; 36 | 37 | public class VideoRecorder extends Recorder implements OnInfoListener, 38 | OnErrorListener { 39 | protected MediaRecorder mMediaRecorder; 40 | protected int mRate; 41 | 42 | public VideoRecorder(RecSettings settings, 43 | Context context, Handler handler) { 44 | super(settings, context, handler); 45 | } 46 | 47 | protected int getFrameRate() { 48 | Camera.Parameters params = mCamera.getParameters(); 49 | 50 | List ranges = params.getSupportedPreviewFpsRange(); 51 | if (ranges == null) 52 | return -1; 53 | 54 | int fps = mSettings.getFrameRate() * 1000; 55 | int selFps = ranges.get(ranges.size() - 1)[1]; 56 | 57 | for (int i = 0; i < ranges.size(); ++i) { 58 | int[] range = ranges.get(i); 59 | 60 | if (fps >= range[0] && fps <= range[1]) { 61 | selFps = fps / 1000; 62 | break; 63 | } 64 | } 65 | 66 | return selFps; 67 | } 68 | 69 | private void releaseMediaRecorder() { 70 | if (mMediaRecorder == null) 71 | return; 72 | 73 | try { 74 | mMediaRecorder.stop(); 75 | mMediaRecorder.reset(); 76 | mMediaRecorder.release(); 77 | } catch (Exception e) { 78 | e.printStackTrace(); 79 | } 80 | mMediaRecorder = null; 81 | } 82 | 83 | @Override 84 | public void stop() { 85 | muteShutter(); 86 | releaseMediaRecorder(); 87 | 88 | super.stop(); 89 | } 90 | 91 | protected void setCameraParams() throws IOException { 92 | Camera.Parameters params = mCamera.getParameters(); 93 | double ratio = (double) mSettings.getFrameWidth() 94 | / mSettings.getFrameHeight(); 95 | /* 96 | * hack necessary for samsung phones to enable 16:9 recordings otherwise 97 | * the camera will record 4:3 image and stretch it to 16:9 98 | */ 99 | if (Math.abs(ratio - (16D / 9D)) < 0.01) 100 | params.set("cam_mode", 1); 101 | 102 | List suppList = params.getSupportedWhiteBalance(); 103 | if (suppList != null) { 104 | Set suppModes = new HashSet(); 105 | suppModes.addAll(suppList); 106 | 107 | if (suppModes.contains(Camera.Parameters.WHITE_BALANCE_AUTO)) { 108 | params.setWhiteBalance(Camera.Parameters.WHITE_BALANCE_AUTO); 109 | } 110 | } 111 | 112 | suppList = params.getSupportedFocusModes(); 113 | if (suppList != null) { 114 | Set suppModes = new HashSet(); 115 | suppModes.addAll(suppList); 116 | 117 | if (suppModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) { 118 | params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO); 119 | } 120 | } 121 | params.setExposureCompensation(mSettings.getExposureCompensation()); 122 | params.setZoom(mSettings.getZoom()); 123 | mCamera.setParameters(params); 124 | } 125 | 126 | @Override 127 | protected void prepareRecord() throws IOException { 128 | releaseMediaRecorder(); 129 | 130 | releaseCamera(); 131 | 132 | mCamera = Camera.open(mSettings.getCameraId()); 133 | 134 | setCameraParams(); 135 | 136 | mRate = getFrameRate(); 137 | 138 | SurfaceTexture surfaceTexture = new SurfaceTexture(10); 139 | mCamera.setPreviewTexture(surfaceTexture); 140 | //mCamera.setPreviewDisplay(mSurfaceHolder); 141 | 142 | muteShutter(); 143 | 144 | mCamera.unlock(); 145 | 146 | mMediaRecorder = new MediaRecorder(); 147 | mMediaRecorder.setCamera(mCamera); 148 | } 149 | 150 | @Override 151 | protected void doRecord() throws IllegalStateException, IOException { 152 | mMediaRecorder.setOrientationHint(getCameraRotation(mSettings.getCameraId())); 153 | // no need for more sensor data 154 | disableOrientationSensor(); 155 | 156 | mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); 157 | mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 158 | 159 | CamcorderProfile p = CamcorderProfile.get(mSettings.getCameraId(), 160 | mSettings.getRecProfile()); 161 | p.videoFrameWidth = mSettings.getFrameWidth(); 162 | p.videoFrameHeight = mSettings.getFrameHeight(); 163 | mMediaRecorder.setProfile(p); 164 | 165 | if (mRate != -1) 166 | mMediaRecorder.setVideoFrameRate(mRate); 167 | mMediaRecorder.setOutputFile(getOutputFile("mp4").getAbsolutePath()); 168 | mMediaRecorder.setVideoSize(mSettings.getFrameWidth(), mSettings.getFrameHeight()); 169 | 170 | if (mSettings.getStopRecAfter() > 0) { 171 | mMediaRecorder.setMaxDuration(mSettings.getStopRecAfter()); 172 | mMediaRecorder.setOnInfoListener(this); 173 | } 174 | 175 | Log.i(getClass().getSimpleName(), "Starting video recording"); 176 | mMediaRecorder.setOnErrorListener(this); 177 | if (mSettings.getVideoEncodingBitRate()>0) mMediaRecorder.setVideoEncodingBitRate(mSettings.getVideoEncodingBitRate()); 178 | mMediaRecorder.prepare(); 179 | 180 | mMediaRecorder.start(); 181 | } 182 | 183 | @Override 184 | protected void muteShutter() { 185 | // mCamera.enableShutterSound(false); does not work for MediaRecorder 186 | // (on Samsung Galaxy S3) 187 | if (mSettings != null && mSettings.isMuteShutter() && mMute != null) 188 | mMute.muteShutter(); 189 | } 190 | 191 | @Override 192 | protected void unmuteShutter() { 193 | // mCamera.enableShutterSound(true); does not work for MediaRecorder (on 194 | // Samsung Galaxy S3) 195 | if (mSettings != null && mSettings.isMuteShutter() && mMute != null) 196 | mMute.unmuteShutter(); 197 | } 198 | 199 | @Override 200 | public void onInfo(MediaRecorder mr, int what, int extra) { 201 | switch (what) { 202 | case MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED: 203 | success(); //tell service to stop 204 | break; 205 | } 206 | } 207 | 208 | @Override 209 | public void onError(MediaRecorder mr, int what, int extra) { 210 | switch (what) { 211 | case MediaRecorder.MEDIA_ERROR_SERVER_DIED: 212 | handleError(getClass().getSimpleName(), "Mediaserver died"); 213 | break; 214 | default: 215 | handleError(getClass().getSimpleName(), 216 | "Unkown error occured while recording"); 217 | break; 218 | } 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /app/src/main/java/at/andreasrohner/spartantimelapserec/recorder/VideoTimeLapseRecorder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Spartan Time Lapse Recorder - Minimalistic android time lapse recording app 3 | * Copyright (C) 2014 Andreas Rohner 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package at.andreasrohner.spartantimelapserec.recorder; 20 | 21 | import java.io.IOException; 22 | 23 | import android.content.Context; 24 | import android.media.CamcorderProfile; 25 | import android.media.MediaRecorder; 26 | import android.os.Handler; 27 | import android.util.Log; 28 | import android.widget.Toast; 29 | import at.andreasrohner.spartantimelapserec.R; 30 | import at.andreasrohner.spartantimelapserec.data.RecSettings; 31 | 32 | public class VideoTimeLapseRecorder extends VideoRecorder { 33 | 34 | public VideoTimeLapseRecorder(RecSettings settings, 35 | Context context, Handler handler) { 36 | super(settings, context, handler); 37 | } 38 | 39 | protected void doRecord() throws IllegalStateException, IOException { 40 | mMediaRecorder.setOrientationHint(getCameraRotation(mSettings 41 | .getCameraId())); 42 | // no need for more sensor data 43 | disableOrientationSensor(); 44 | 45 | mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 46 | 47 | CamcorderProfile p = CamcorderProfile.get(mSettings.getCameraId(), 48 | mSettings.getRecProfile()); 49 | p.videoFrameWidth = mSettings.getFrameWidth(); 50 | p.videoFrameHeight = mSettings.getFrameHeight(); 51 | mMediaRecorder.setProfile(p); 52 | 53 | mMediaRecorder.setCaptureRate(1000 / ((double) mSettings.getCaptureRate())); 54 | 55 | if (mRate != -1) 56 | mMediaRecorder.setVideoFrameRate(mRate); 57 | mMediaRecorder.setOutputFile(getOutputFile("mp4").getAbsolutePath()); 58 | mMediaRecorder.setVideoSize(mSettings.getFrameWidth(), 59 | mSettings.getFrameHeight()); 60 | 61 | if (mSettings.getStopRecAfter() > 0) { 62 | if (mRate != -1) { 63 | mRate = CamcorderProfile.get(mSettings.getCameraId(), 64 | mSettings.getRecProfile()).videoFrameRate; 65 | } 66 | 67 | int duration = (int) (((double) (mSettings.getStopRecAfter() / mSettings.getCaptureRate())) / mRate * 1000); //Duration of the video 68 | if (duration < 500) { 69 | handleError(getClass().getSimpleName(), 70 | mContext.getString(R.string.pref_stop_recording_after) 71 | + " is too short in relation to the " 72 | + mContext.getString(R.string.pref_capture_rate)); 73 | Toast.makeText(mContext, mContext.getString(R.string.error_too_short), Toast.LENGTH_SHORT).show(); 74 | return; 75 | } 76 | 77 | mMediaRecorder.setMaxDuration(duration); 78 | mMediaRecorder.setOnInfoListener(this); 79 | } 80 | 81 | Log.i(getClass().getSimpleName(), "Starting video recording"); 82 | mMediaRecorder.setOnErrorListener(this); 83 | if (mSettings.getVideoEncodingBitRate()>0) mMediaRecorder.setVideoEncodingBitRate(mSettings.getVideoEncodingBitRate()); 84 | mMediaRecorder.prepare(); 85 | 86 | mMediaRecorder.start(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /app/src/main/java/at/andreasrohner/spartantimelapserec/rest/HttpOutput.java: -------------------------------------------------------------------------------- 1 | package at.andreasrohner.spartantimelapserec.rest; 2 | 3 | import java.io.IOException; 4 | 5 | /** 6 | * HTTP Output writer 7 | */ 8 | public interface HttpOutput { 9 | 10 | /** 11 | * Send HTTP Header 12 | * 13 | * @param code Code 14 | * @param contentType Content Type 15 | * @throws IOException 16 | */ 17 | public void sendReplyHeader(ReplyCode code, String contentType) throws IOException; 18 | 19 | /** 20 | * Send Line 21 | * 22 | * @param line Line 23 | */ 24 | public void sendLine(String line) throws IOException; 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/at/andreasrohner/spartantimelapserec/rest/ListFolderHtml.java: -------------------------------------------------------------------------------- 1 | package at.andreasrohner.spartantimelapserec.rest; 2 | 3 | import android.util.Log; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | 8 | /** 9 | * List Folder user clickable 10 | */ 11 | public class ListFolderHtml extends ListFolderPlain { 12 | 13 | /** 14 | * Current listing folder 15 | */ 16 | private String listFolder; 17 | 18 | /** 19 | * Constructor 20 | * 21 | * @param out Output 22 | * @param rootDir Root directory 23 | */ 24 | public ListFolderHtml(HttpOutput out, File rootDir) { 25 | super(out, rootDir); 26 | } 27 | 28 | @Override 29 | public boolean output(String listFolder) throws IOException { 30 | this.listFolder = listFolder; 31 | boolean result = super.output(listFolder); 32 | out.sendLine(""); 33 | 34 | return result; 35 | } 36 | 37 | @Override 38 | protected void writeHeader() throws IOException { 39 | out.sendReplyHeader(ReplyCode.FOUND, "text/html"); 40 | out.sendLine("TimeLapseCam"); 41 | out.sendLine("

List folder

"); 42 | } 43 | 44 | @Override 45 | protected void writeFile(File file) throws IOException { 46 | // No escaping... The File should not contiain special chars, so keep it simple 47 | if (file.isDirectory()) { 48 | out.sendLine("" + file.getName() + "
"); 49 | } else { 50 | out.sendLine("" + file.getName() + "
"); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/at/andreasrohner/spartantimelapserec/rest/ListFolderPlain.java: -------------------------------------------------------------------------------- 1 | package at.andreasrohner.spartantimelapserec.rest; 2 | 3 | import android.util.Log; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | 8 | /** 9 | * List Folder for REST API 10 | */ 11 | public class ListFolderPlain { 12 | 13 | /** 14 | * Log Tag 15 | */ 16 | protected static final String TAG = HttpThread.class.getSimpleName(); 17 | 18 | /** 19 | * Output 20 | */ 21 | protected final HttpOutput out; 22 | 23 | /** 24 | * Root directory 25 | */ 26 | protected final File rootDir; 27 | 28 | /** 29 | * Constructor 30 | * 31 | * @param out Output 32 | * @param rootDir Root directory 33 | */ 34 | public ListFolderPlain(HttpOutput out, File rootDir) { 35 | this.out = out; 36 | this.rootDir = rootDir; 37 | } 38 | 39 | public boolean output(String listFolder) throws IOException { 40 | File listDir = new File(rootDir, listFolder); 41 | Log.d(TAG, "List dir: " + listDir); 42 | if (!listDir.isDirectory()) { 43 | Log.w(TAG, "Directory does not exists"); 44 | return false; 45 | } 46 | 47 | File[] files = listDir.listFiles(); 48 | if (files == null) { 49 | Log.w(TAG, "Directory could not be listed"); 50 | return false; 51 | } 52 | 53 | writeHeader(); 54 | 55 | for (File f : files) { 56 | if (f.getName().charAt(0) == '.') { 57 | continue; 58 | } 59 | 60 | writeFile(f); 61 | } 62 | 63 | return true; 64 | } 65 | 66 | /** 67 | * Write out HTTP Header 68 | */ 69 | protected void writeHeader() throws IOException { 70 | out.sendReplyHeader(ReplyCode.FOUND, "text/plain"); 71 | } 72 | 73 | /** 74 | * Write out a file 75 | * 76 | * @param file File 77 | */ 78 | protected void writeFile(File file) throws IOException { 79 | out.sendLine(file.getName()); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/java/at/andreasrohner/spartantimelapserec/rest/ReplyCode.java: -------------------------------------------------------------------------------- 1 | package at.andreasrohner.spartantimelapserec.rest; 2 | 3 | /** 4 | * HTTP Reply codes 5 | */ 6 | public enum ReplyCode { 7 | 8 | /** 9 | * Found 10 | */ 11 | FOUND(200, "Found"), 12 | 13 | /** 14 | * Forbidden 15 | */ 16 | FORBIDDEN(403, "Forbidden"), 17 | 18 | /** 19 | * File was not found 20 | */ 21 | NOT_FOUND(404, "Not found"); 22 | 23 | /** 24 | * HTTP Code 25 | */ 26 | public final int code; 27 | 28 | /** 29 | * Result Text 30 | */ 31 | public final String text; 32 | 33 | /** 34 | * Constructor 35 | * 36 | * @param code HTTP Code 37 | * @param text Result Text 38 | */ 39 | private ReplyCode(int code, String text) { 40 | this.code = code; 41 | this.text = text; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/at/andreasrohner/spartantimelapserec/rest/TcpListener.java: -------------------------------------------------------------------------------- 1 | package at.andreasrohner.spartantimelapserec.rest; 2 | 3 | import java.net.ServerSocket; 4 | import java.net.Socket; 5 | 6 | import android.util.Log; 7 | 8 | 9 | /** 10 | * HTTP REST API Service, TCP Listener Service 11 | *

12 | * Inspirated by SwiFTP https://github.com/ppareit/swiftp/blob/master/app/src/main/java/be/ppareit/swiftp/server/TcpListener.java 13 | *

14 | * (c) Andreas Butti, 2024 15 | */ 16 | public class TcpListener extends Thread { 17 | 18 | /** 19 | * Log Tag 20 | */ 21 | private static final String TAG = TcpListener.class.getSimpleName(); 22 | 23 | /** 24 | * Socket for incoming connections 25 | */ 26 | private ServerSocket listenSocket; 27 | 28 | /** 29 | * HTTP REST API Service 30 | */ 31 | private RestService restService; 32 | 33 | /** 34 | * Constructor 35 | * 36 | * @param listenSocket Socket for incoming connections 37 | * @param restService HTTP REST API Service 38 | */ 39 | public TcpListener(ServerSocket listenSocket, RestService restService) { 40 | this.listenSocket = listenSocket; 41 | this.restService = restService; 42 | } 43 | 44 | /** 45 | * Close listening Socket 46 | */ 47 | public void quit() { 48 | try { 49 | listenSocket.close(); // if the TcpListener thread is blocked on accept, 50 | // closing the socket will raise an exception 51 | } catch (Exception e) { 52 | Log.w(TAG, "Exception closing TcpListener listenSocket",e); 53 | } 54 | } 55 | 56 | @Override 57 | public void run() { 58 | try { 59 | while (true) { 60 | Socket clientSocket = listenSocket.accept(); 61 | Log.i(TAG, "New connection from "+ clientSocket.getRemoteSocketAddress()); 62 | HttpThread newSession = new HttpThread(clientSocket, restService); 63 | newSession.start(); 64 | restService.registerSessionThread(newSession); 65 | } 66 | } catch (Exception e) { 67 | Log.d(TAG, "Exception in TcpListener", e); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /app/src/main/java/at/andreasrohner/spartantimelapserec/sensor/CameraSettings.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Spartan Time Lapse Recorder - Minimalistic android time lapse recording app 3 | * Copyright (C) 2014 Andreas Rohner 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package at.andreasrohner.spartantimelapserec.sensor; 20 | 21 | import java.util.ArrayList; 22 | import java.util.Collections; 23 | import java.util.Comparator; 24 | import java.util.List; 25 | import java.util.Set; 26 | import java.util.TreeSet; 27 | import android.content.SharedPreferences; 28 | import android.hardware.Camera; 29 | import android.hardware.Camera.Size; 30 | import android.media.CamcorderProfile; 31 | 32 | public class CameraSettings { 33 | 34 | private Camera.Parameters[] cameraParams; 35 | 36 | private synchronized Camera.Parameters getCameraParameters(int camId) { 37 | if (cameraParams == null) 38 | cameraParams = new Camera.Parameters[Camera.getNumberOfCameras()]; 39 | 40 | Camera.Parameters params = cameraParams[camId]; 41 | if (params == null) { 42 | Camera camera = Camera.open(camId); 43 | params = camera.getParameters(); 44 | camera.release(); 45 | cameraParams[camId] = params; 46 | } 47 | 48 | return params; 49 | } 50 | 51 | private Set getStringSet(SharedPreferences prefs, String key, 52 | Set defValues) { 53 | return prefs.getStringSet(key, defValues); 54 | } 55 | 56 | private void putStringSet(SharedPreferences prefs, String key, 57 | Set set) { 58 | prefs.edit().putStringSet(key, set).commit(); 59 | return; 60 | 61 | } 62 | 63 | private void addProfileFrameRate(int camId, List list, int profile) { 64 | try { 65 | if (CamcorderProfile.hasProfile(camId, profile)) { 66 | CamcorderProfile p = CamcorderProfile.get(camId, profile); 67 | list.add(p.videoFrameRate); 68 | } 69 | } catch (Exception e) { 70 | } 71 | } 72 | 73 | private List getFrameRatesFromCameraProfile(int camId) { 74 | List list = new ArrayList(); 75 | 76 | addProfileFrameRate(camId, list, CamcorderProfile.QUALITY_HIGH); 77 | addProfileFrameRate(camId, list, CamcorderProfile.QUALITY_LOW); 78 | addProfileFrameRate(camId, list, CamcorderProfile.QUALITY_CIF); 79 | addProfileFrameRate(camId, list, CamcorderProfile.QUALITY_1080P); 80 | addProfileFrameRate(camId, list, CamcorderProfile.QUALITY_720P); 81 | addProfileFrameRate(camId, list, CamcorderProfile.QUALITY_480P); 82 | addProfileFrameRate(camId, list, CamcorderProfile.QUALITY_QCIF); 83 | addProfileFrameRate(camId, list, CamcorderProfile.QUALITY_QVGA); 84 | 85 | // deduplicate sizes 86 | Set set = new TreeSet(list); 87 | list.clear(); 88 | list.addAll(set); 89 | 90 | // sort 91 | Collections.sort(list); 92 | 93 | return list; 94 | } 95 | 96 | private List getFrameRatesFromCamera(int camId) { 97 | Camera.Parameters params = getCameraParameters(camId); 98 | 99 | List ranges = params.getSupportedPreviewFpsRange(); 100 | List fpsList = new ArrayList(); 101 | int prevFps = -1; 102 | 103 | if (ranges == null) 104 | return getFrameRatesFromCameraProfile(camId); 105 | 106 | for (int i = 0; i < ranges.size(); ++i) { 107 | int fps = ranges.get(i)[Camera.Parameters.PREVIEW_FPS_MAX_INDEX] / 1000; 108 | if (prevFps == fps) 109 | continue; 110 | prevFps = fps; 111 | 112 | fpsList.add(fps); 113 | } 114 | 115 | return fpsList; 116 | } 117 | 118 | public void prefetch(final SharedPreferences prefs) { 119 | new Thread(new Runnable() { 120 | @Override 121 | public void run() { 122 | for (int i = 0; i < Camera.getNumberOfCameras(); ++i) { 123 | getFrameRates(prefs, i); 124 | } 125 | } 126 | }).start(); 127 | } 128 | 129 | public List getFrameRates(SharedPreferences prefs, int camId) { 130 | List fpsIntList; 131 | Set fpsList = getStringSet(prefs, "pref_frame_rate_values_" 132 | + camId, null); 133 | 134 | if (fpsList == null) { 135 | fpsList = new TreeSet(); 136 | fpsIntList = getFrameRatesFromCamera(camId); 137 | 138 | for (Integer fps : fpsIntList) { 139 | fpsList.add(fps.toString()); 140 | } 141 | 142 | putStringSet(prefs, "pref_frame_rate_values_" + camId, fpsList); 143 | return fpsIntList; 144 | } 145 | 146 | fpsIntList = new ArrayList(); 147 | for (String fps : fpsList) { 148 | try { 149 | fpsIntList.add(Integer.valueOf(fps)); 150 | } catch (NumberFormatException e) { 151 | } 152 | } 153 | 154 | Collections.sort(fpsIntList); 155 | 156 | return fpsIntList; 157 | } 158 | 159 | private void addProfileFrameSize(int camId, List list, int profile) { 160 | try { 161 | if (CamcorderProfile.hasProfile(camId, profile)) { 162 | CamcorderProfile p = CamcorderProfile.get(camId, profile); 163 | list.add(new int[] { p.videoFrameWidth, p.videoFrameHeight }); 164 | } 165 | } catch (Exception e) { 166 | } 167 | } 168 | 169 | private void prepareSizeList(List sizes) { 170 | Comparator comp = new Comparator() { 171 | @Override 172 | public int compare(int[] lhs, int[] rhs) { 173 | if (lhs[0] > rhs[0]) 174 | return 1; 175 | if (lhs[0] < rhs[0]) 176 | return -1; 177 | if (lhs[1] > rhs[1]) 178 | return 1; 179 | if (lhs[1] < rhs[1]) 180 | return -1; 181 | return 0; 182 | } 183 | }; 184 | 185 | // deduplicate sizes 186 | Set set = new TreeSet(comp); 187 | set.addAll(sizes); 188 | sizes.clear(); 189 | sizes.addAll(set); 190 | 191 | // sort 192 | Collections.sort(sizes, comp); 193 | } 194 | 195 | private List getFrameSizesFromProfiles(int camId, boolean timeLapse) { 196 | List list = new ArrayList(); 197 | 198 | if (timeLapse) { 199 | addProfileFrameSize(camId, list, 200 | CamcorderProfile.QUALITY_TIME_LAPSE_HIGH); 201 | addProfileFrameSize(camId, list, 202 | CamcorderProfile.QUALITY_TIME_LAPSE_LOW); 203 | addProfileFrameSize(camId, list, 204 | CamcorderProfile.QUALITY_TIME_LAPSE_CIF); 205 | addProfileFrameSize(camId, list, 206 | CamcorderProfile.QUALITY_TIME_LAPSE_1080P); 207 | addProfileFrameSize(camId, list, 208 | CamcorderProfile.QUALITY_TIME_LAPSE_720P); 209 | addProfileFrameSize(camId, list, 210 | CamcorderProfile.QUALITY_TIME_LAPSE_480P); 211 | addProfileFrameSize(camId, list, 212 | CamcorderProfile.QUALITY_TIME_LAPSE_QCIF); 213 | addProfileFrameSize(camId, list, 214 | CamcorderProfile.QUALITY_TIME_LAPSE_QVGA); 215 | } else { 216 | addProfileFrameSize(camId, list, CamcorderProfile.QUALITY_HIGH); 217 | addProfileFrameSize(camId, list, CamcorderProfile.QUALITY_LOW); 218 | addProfileFrameSize(camId, list, CamcorderProfile.QUALITY_CIF); 219 | addProfileFrameSize(camId, list, CamcorderProfile.QUALITY_1080P); 220 | addProfileFrameSize(camId, list, CamcorderProfile.QUALITY_720P); 221 | addProfileFrameSize(camId, list, CamcorderProfile.QUALITY_480P); 222 | addProfileFrameSize(camId, list, CamcorderProfile.QUALITY_QCIF); 223 | addProfileFrameSize(camId, list, CamcorderProfile.QUALITY_QVGA); 224 | } 225 | 226 | prepareSizeList(list); 227 | 228 | return list; 229 | } 230 | 231 | public List getFrameSizes(SharedPreferences prefs, int camId, 232 | boolean timeLapse) { 233 | 234 | Set sizes = getStringSet(prefs, "pref_frame_size_values_" 235 | + camId, null); 236 | List sizesList = new ArrayList(); 237 | 238 | if (sizes == null) { 239 | Camera.Parameters params = getCameraParameters(camId); 240 | 241 | List suppSizes = params.getSupportedVideoSizes(); 242 | if (suppSizes == null) 243 | return getFrameSizesFromProfiles(camId, timeLapse); 244 | 245 | sizes = new TreeSet(); 246 | for (Size s : suppSizes) { 247 | sizes.add(s.width + "x" + s.height); 248 | sizesList.add(new int[] { s.width, s.height }); 249 | } 250 | 251 | putStringSet(prefs, "pref_frame_size_values_" + camId, sizes); 252 | 253 | prepareSizeList(sizesList); 254 | 255 | return sizesList; 256 | } 257 | 258 | for (String s : sizes) { 259 | String[] tmp = s.split("x"); 260 | sizesList.add(new int[] { Integer.valueOf(tmp[0]), 261 | Integer.valueOf(tmp[1]) }); 262 | } 263 | 264 | prepareSizeList(sizesList); 265 | 266 | return sizesList; 267 | } 268 | 269 | public List getPictureSizes(SharedPreferences prefs, int camId) { 270 | Set sizes = getStringSet(prefs, "pref_picture_size_values_" 271 | + camId, null); 272 | List sizesList = new ArrayList(); 273 | 274 | if (sizes == null) { 275 | Camera.Parameters params = getCameraParameters(camId); 276 | 277 | sizes = new TreeSet(); 278 | for (Size s : params.getSupportedPictureSizes()) { 279 | sizes.add(s.width + "x" + s.height); 280 | sizesList.add(new int[] { s.width, s.height }); 281 | } 282 | 283 | putStringSet(prefs, "pref_picture_size_values_" + camId, sizes); 284 | 285 | prepareSizeList(sizesList); 286 | 287 | return sizesList; 288 | } 289 | 290 | for (String s : sizes) { 291 | String[] tmp = s.split("x"); 292 | sizesList.add(new int[] { Integer.valueOf(tmp[0]), 293 | Integer.valueOf(tmp[1]) }); 294 | } 295 | 296 | prepareSizeList(sizesList); 297 | 298 | return sizesList; 299 | } 300 | 301 | public int getMinExposureCompensation(int camId){ 302 | Camera.Parameters params = getCameraParameters(camId); 303 | return params.getMinExposureCompensation(); 304 | } 305 | 306 | public int getMaxExposureCompensation(int camId){ 307 | Camera.Parameters params = getCameraParameters(camId); 308 | return params.getMaxExposureCompensation(); 309 | } 310 | 311 | public int getMaxZoom(int camId){ 312 | Camera.Parameters params = getCameraParameters(camId); 313 | if (params.isZoomSupported()) 314 | return params.getMaxZoom(); 315 | else 316 | return 0; 317 | } 318 | 319 | } 320 | -------------------------------------------------------------------------------- /app/src/main/java/at/andreasrohner/spartantimelapserec/sensor/MuteShutter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Spartan Time Lapse Recorder - Minimalistic android time lapse recording app 3 | * Copyright (C) 2014 Andreas Rohner 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package at.andreasrohner.spartantimelapserec.sensor; 20 | 21 | import android.app.NotificationManager; 22 | import android.content.Context; 23 | import android.media.AudioManager; 24 | import android.os.Handler; 25 | 26 | public class MuteShutter { 27 | private int mode; 28 | private int ringerMode; 29 | private static final int[] streams = new int[] { AudioManager.STREAM_DTMF, 30 | AudioManager.STREAM_MUSIC, AudioManager.STREAM_NOTIFICATION, 31 | AudioManager.STREAM_SYSTEM }; 32 | private int[] streamVolumes = new int[streams.length]; 33 | private AudioManager audioManager; 34 | private NotificationManager notificationManager; 35 | private final Handler handler = new Handler(); 36 | private volatile boolean muted; 37 | 38 | public MuteShutter(Context context) { 39 | this.audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 40 | this.notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); 41 | muted = false; 42 | } 43 | 44 | private void storeSoundSettings() { 45 | mode = audioManager.getMode(); 46 | ringerMode = audioManager.getRingerMode(); 47 | 48 | for (int i = 0; i < streams.length; ++i) 49 | streamVolumes[i] = audioManager.getStreamVolume(streams[i]); 50 | } 51 | 52 | private void recoverSoundSettings() { 53 | audioManager.setMode(mode); 54 | audioManager.setRingerMode(ringerMode); 55 | 56 | for (int i = 0; i < streams.length; ++i) 57 | audioManager.setStreamVolume(streams[i], streamVolumes[i], 58 | AudioManager.FLAG_ALLOW_RINGER_MODES); 59 | } 60 | 61 | public void maxAllStreams() { 62 | for (int stream : streams) { 63 | audioManager.setStreamMute(stream, false); 64 | audioManager.setStreamVolume(stream, audioManager.getStreamMaxVolume(stream), 65 | AudioManager.FLAG_ALLOW_RINGER_MODES); 66 | } 67 | } 68 | 69 | public synchronized void unmuteShutter() { 70 | if (!muted) 71 | return; 72 | 73 | maxAllStreams(); 74 | 75 | recoverSoundSettings(); 76 | muted = false; 77 | } 78 | 79 | private void restartHandler() { 80 | // remove all Runnables 81 | handler.removeCallbacksAndMessages(null); 82 | handler.postDelayed(new Runnable() { 83 | public void run() { 84 | unmuteShutter(); 85 | } 86 | }, 2000); 87 | } 88 | 89 | public synchronized void muteShutter() { 90 | if (muted) { 91 | restartHandler(); 92 | return; 93 | } 94 | 95 | storeSoundSettings(); 96 | 97 | 98 | if (notificationManager.isNotificationPolicyAccessGranted()) { 99 | for (int stream : streams) { 100 | audioManager.setStreamVolume(stream, 0, 101 | AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE); 102 | audioManager.setStreamMute(stream, true); 103 | } 104 | audioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT); 105 | } 106 | 107 | muted = true; 108 | 109 | restartHandler(); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /app/src/main/java/at/andreasrohner/spartantimelapserec/sensor/OrientationSensor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Spartan Time Lapse Recorder - Minimalistic android time lapse recording app 3 | * Copyright (C) 2014 Andreas Rohner 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package at.andreasrohner.spartantimelapserec.sensor; 20 | 21 | import android.content.Context; 22 | import android.hardware.Camera; 23 | import android.hardware.Camera.CameraInfo; 24 | import android.view.OrientationEventListener; 25 | 26 | public class OrientationSensor extends OrientationEventListener { 27 | private int mOrientation; 28 | 29 | public OrientationSensor(Context context) { 30 | super(context); 31 | 32 | } 33 | 34 | @Override 35 | public void onOrientationChanged(int orientation) { 36 | if (orientation != ORIENTATION_UNKNOWN) 37 | mOrientation = orientation; 38 | } 39 | 40 | public int getCameraRotation(int cameraId) { 41 | Camera.CameraInfo info = new Camera.CameraInfo(); 42 | Camera.getCameraInfo(cameraId, info); 43 | 44 | mOrientation = (mOrientation + 45) / 90 * 90; 45 | int rotation = 0; 46 | if (info.facing == CameraInfo.CAMERA_FACING_FRONT) { 47 | rotation = (info.orientation - mOrientation + 360) % 360; 48 | } else { // back-facing camera 49 | rotation = (info.orientation + mOrientation) % 360; 50 | } 51 | 52 | return rotation; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_camera.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 20 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 17 | 24 | 31 | 38 | 45 | 52 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_monochrome.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 9 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_radio_button_checked_24px.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_radio_button_checked_disabled_24px.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_stop_circle_24px.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_stop_circle_disabled_24px.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_visibility_24px.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_visibility_disabled_24px.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_date_preference.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 10 | 11 | 15 | 16 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_date_widget_preference.xml: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_intervalpicker_preference.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 14 | 15 | 21 | 22 | 28 | 33 | 38 | 39 | 45 | 50 | 55 | 56 | 62 | 67 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_seekbar_preference.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 12 | 13 | 19 | 20 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/menu/main.xml: -------------------------------------------------------------------------------- 1 |

5 | 6 | 12 | 18 | 22 | 28 | 32 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woheller69/TimeLapseCamera/dbec6562463db540398f4b3b4c1442e5fecfb0c3/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woheller69/TimeLapseCamera/dbec6562463db540398f4b3b4c1442e5fecfb0c3/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woheller69/TimeLapseCamera/dbec6562463db540398f4b3b4c1442e5fecfb0c3/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woheller69/TimeLapseCamera/dbec6562463db540398f4b3b4c1442e5fecfb0c3/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woheller69/TimeLapseCamera/dbec6562463db540398f4b3b4c1442e5fecfb0c3/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/raw/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woheller69/TimeLapseCamera/dbec6562463db540398f4b3b4c1442e5fecfb0c3/app/src/main/res/raw/favicon.ico -------------------------------------------------------------------------------- /app/src/main/res/raw/help.txt: -------------------------------------------------------------------------------- 1 | HELP 2 | 3 | GET /: Show this Help 4 | 5 | REST API v1: 6 | GET /1/ctrl/status: Get current state: [stopped/running] 7 | GET /1/ctrl/start: Start recording 8 | GET /1/ctrl/stop: Stop recording 9 | GET /1/ctrl/param: Get parameter 10 | GET /1/device/battery: Get battery percentage 11 | GET /1/current/img: Current / last recorded image 12 | GET /1/current/imgcount: Image count 13 | GET /1/current/lastimg: Last image: Name, Timestamp and URL 14 | GET /1/img/list: List image folders 15 | GET /1/img/listhtml: user clickable HTML page 16 | GET /1/img//list: List folder / images 17 | GET /1/img///list: List folder / images 18 | GET /1/img//.../: Download image 19 | -------------------------------------------------------------------------------- /app/src/main/res/raw/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 108 | 109 | 384 | TimeLapseCam 385 | 386 | 387 | 388 |

TimeLapseCam

389 |
390 |
START
391 |
STOP
392 |
REFRESH
393 |
AUTOREFRESH
394 |
395 | 397 |
398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 |
Last refresh---
State---
Battery---
Mode---
Last image---
Image count---
Next image---
Interval---
432 |
433 |
434 | Refresh to get current image / No image to display 435 |
436 |
437 | 440 | 441 | 442 | -------------------------------------------------------------------------------- /app/src/main/res/values-de/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Normales Video (MP4) 5 | Zeitraffer Video (MP4) 6 | Zeitraffer Bilder (JPG) 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values-de/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | TimeLapseCam 4 | Start 5 | Stop 6 | Lautstärke zurücksetzen 7 | Galerie öffnen 8 | Galerie öffnen 9 | Vorschau öffnen 10 | Aufnahme starten 11 | Aufnahme stoppen 12 | Lautstärke zurücksetzen 13 | Einstellungen 14 | Galerie öffnen 15 | Vorschau öffnen 16 | Aufnahme läuft 17 | Verhindere Kameraauslösegeräusch & Klingelton 18 | Versucht, das Telefon auf stumm zu schalten um das Auslösegeräusch der Kamera zu unterdrücken. Versucht außerdem, den Klingelton des Telefons abzustellen. Benötigt separate Berechtigung! 19 | Kamera auswählen 20 | Kamera 21 | vorne 22 | hinten 23 | Aufnahmemodus 24 | Projekttitel 25 | Der Projekttitel wird zur Erzeugung eines Verzeichnisses für die Aufnahmen verwendet 26 | Video Abspielrate 27 | Auflösung 28 | Kamera Abtastintervall 29 | Das Intervall in dem Bilder aufgenommen werden 30 | Anfangsverzögerung 31 | Auswahl einer Verzögerung bevor die Aufnahme beginnt 32 | JPEG Qualität 33 | Auswahl der JPEG Qualität (die Einstellung hat auf den meisten Geräten keinen Effekt) 34 | Wenig Akku 35 | Aufnahme stoppen wenn der Ladestand des Akkus gering ist 36 | Wenig Speicherplatz 37 | Aufnahme stoppen wenn der Speicherplatz zur Neige geht 38 | Aufnahme Planen 39 | Aufnahme stoppen nach 40 | Unendlich 41 | Bild 42 | Projekt Einstellungen 43 | Kamera Einstellungen 44 | Planung Einstellungen 45 | Benachrichtigungs Icon 46 | Auswahl des Icons das während der Aufnahme erscheint 47 | fps 48 | ms 49 | s 50 | s 51 | min 52 | min 53 | std 54 | std 55 | Belichtungskorrektur 56 | Aufnahme wird vorbereitet 57 | Berechtigung fehlt 58 | Läuft bereits 59 | Zoom 60 | 61 | 62 | OK 63 | Nein 64 | Vielleicht später 65 | Mögen Sie diese App? Bitte vergeben Sie einen Stern auf GitHub oder spendieren Sie dem Entwickler einen Kaffee über PayPal. 66 | Hardware spezifische Einstellungen 67 | Verzögerung für Kamerastart. Wenn zu niedrig, kann es zum Absturz kommen! 68 | Verzögerung, bevor die Kamera ein Bild aufnimmt. Wenn zu niedrig, kann das Bild dunkel sein! 69 | Verzögerung für Kamerainitialisierung 70 | Verzögerung für Kameraauslöser 71 | Über 72 | Blitz verwenden, falls verfügbar 73 | Für maximale Rate auf 0 setzen (Voreinstellung). Die tatsächliche Bitrate kann durch die Mindestqualität des Codecs beeinflusst werden. 74 | %s bit/s 75 | Beste Qualität 76 | Wert in bit/s eingeben 77 | Bitrate der Videocodierung 78 | Video zu kurz 79 | REST API 80 | REST API aktiviert 81 | HTTP REST API Server aktivieren für Fernsteuerung 82 | HTTP Server Port 83 | Passwort 84 | Port 85 | Port, 1024 - 65535, Standard 8085 86 | Passwort für den REST API Zugriff 87 | Passwort 88 | REST API gestartet... 89 | REST API gestoppt... 90 | Port ungültig, 8085 wird verwendet 91 | Verbindungsinformationen 92 | Keine IP! Berühren zum neu laden. 93 | REST API Läuft 94 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/values-tr/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | TimeLapseCam 4 | Başlat 5 | Durdur 6 | Ses Seviyesini Sıfırla 7 | Galeriyi Aç 8 | Galeriyi Aç 9 | Ön İzlemeyi Aç 10 | Kaydı Başlat 11 | Kaydı Durdur 12 | Ses Seviyesini Sıfırla 13 | Ayarlar 14 | Galeriyi Aç 15 | Ön İzlemeyi Aç 16 | Kayıt devam ediyor 17 | Deklanşör ve Zil Sesini Kapat 18 | Fotoğraf çekerken deklanşör sesini çalmasını önlemek için telefonu sessize almaya çalışır. Ayrıca telefonun zil sesini de kapatmaya çalışır. İzin gerekir! 19 | Kamera Seç 20 | Kamera 21 | ön 22 | arka 23 | Kayıt Modu 24 | Proje Adı 25 | Proje adı, oluşturulan dosyalar için dizin adı ve ön ek olarak kullanılır 26 | Oynatım Kare Hızı 27 | Kare Boyutu 28 | Kamera Çekim Aralığı 29 | Çekim aralığını seçin 30 | Başlangıç Gecikmesi 31 | Çekim başlamadan önceki gecikmeyi seçin 32 | JPEG Kalitesi 33 | JPEG kalitesini seçin (bu ayarın bazı aygıtlarda etkisi yoktur) 34 | Düşük Pil 35 | Pil seviyesi azaldığında kaydı durdurun 36 | Düşük Depolama Alanı 37 | Depolama alanı azalırsa kaydı durdurun 38 | Kaydı Zamanla 39 | Şundan Sonra Kaydı Durdur 40 | Sonsuz 41 | Simge 42 | Proje ayarları 43 | Kamera ayarları 44 | Zamanlama ayarları 45 | Bildirim Simgesi 46 | Kayıt sırasında gösterilen bildirim simgesini seçin 47 | fps 48 | ms 49 | sn 50 | sn 51 | dak 52 | dak 53 | sa 54 | sa 55 | Poz Dengelemesi 56 | Kayıt hazırlanıyor 57 | Eksik izin 58 | Zaten çalışıyor 59 | Yakınlaştır 60 | 61 | 62 | Tamam 63 | Hayır 64 | Belki daha sonra 65 | Bu uygulamayı beğendiniz mi? Lütfen GitHub\'da yıldız verin veya geliştiriciye PayPal aracılığıyla bir kahve ısmarlayın. 66 | Donanıma özel ayarlar 67 | Kamera başlangıcı için gecikme. Çok küçükse çökme meydana gelebilir! 68 | Kamera fotoğraf çekmeden önceki gecikme. Çok düşükse, görüntü karanlık olabilir! 69 | Kamera başlangıç gecikmesi 70 | Kamera çekim gecikmesi 71 | Hakkında 72 | Varsa flaş kullan 73 | En yüksek hız için 0 olarak ayarlayın (öntanımlı). Gerçek bit hızı, kod çözücünün gerektirdiği en düşük kaliteden etkilenebilir. 74 | %s bit/sn 75 | En iyi kalite 76 | bit/sn cinsinden değer girin 77 | Video Kodlama Bit Hızı 78 | Video çok kısa 79 | REST API 80 | REST API etkinleştirildi 81 | Uzaktan denetim için HTTP REST API sunucusunu etkinleştirin 82 | HTTP sunucusu bağlantı noktası 83 | Parola 84 | Bağlantı noktası 85 | Bağlantı noktası, 1024 - 65535, öntanımlı 8085 86 | REST API erişimi için parola 87 | Parola 88 | REST API başlatıldı... 89 | REST API durduruldu... 90 | Bağlantı noktası geçersiz, 8085 kullan 91 | Bağlantı Bilgileri 92 | IP yok! Yenilemek için dokunun. 93 | REST API Çalışıyor 94 | 95 | -------------------------------------------------------------------------------- /app/src/main/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Normal Video (MP4) 5 | Time Lapsed Video (MP4) 6 | Time Lapsed Images (JPG) 7 | 8 | 9 | VIDEO 10 | VIDEO_TIME_LAPSE 11 | IMAGE_TIME_LAPSE 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14sp 5 | 14sp 6 | 20sp 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | TimeLapseCam 4 | Start 5 | Stop 6 | Reset Audio Volume 7 | Open Gallery 8 | Open Gallery 9 | Open Preview 10 | Start Recording 11 | Stop Recording 12 | Reset Audio Volume 13 | Settings 14 | Open Gallery 15 | Open Preview 16 | Recording is running 17 | Mute Shutter Sound & Ringtone 18 | Tries to mute the phone to prevent it from playing the shutter sound while taking a picture. Also tries to mute the ringtone of the phone. Need permission! 19 | Select Camera 20 | Camera 21 | front 22 | back 23 | Recording Mode 24 | Project Title 25 | The project title is used as a directory and prefix for the created files 26 | Playback Frame Rate 27 | Frame Size 28 | Camera Capture Interval 29 | Select the capture interval 30 | Initial Delay 31 | Select the delay before capturing starts 32 | JPEG Quality 33 | Select the JPEG quality (this setting has no effect on some devices) 34 | Low Battery 35 | Stop recording if the battery level gets low 36 | Low Storage Space 37 | Stop recording if the storage space gets low 38 | Schedule Recording 39 | Stop Recording After 40 | Infinite 41 | Icon 42 | Project settings 43 | Camera settings 44 | Scheduling settings 45 | Notification Icon 46 | Select the notification icon shown while recording 47 | fps 48 | ms 49 | sec 50 | secs 51 | min 52 | mins 53 | hour 54 | hours 55 | Exposure Compensation 56 | Preparing recording 57 | Missing permission 58 | Already running 59 | Zoom 60 | 61 | 62 | OK 63 | No 64 | Maybe later 65 | Do you like this app? Please give a star on GitHub or buy the developer a coffee via PayPal. 66 | Hardware specific settings 67 | Delay for camera startup. If too small crash can occur! 68 | Delay before camera takes picture. If too low, image may be dark! 69 | Camera init delay 70 | Camera trigger delay 71 | About 72 | Use Flash if available 73 | Set 0 for maximum rate (default). The actual bitrate may be affected by the minimum quality required by the codec. 74 | %s bit/s 75 | Best quality 76 | Enter value in bit/s 77 | Video Encoding Bit Rate 78 | Video too short 79 | REST API 80 | REST API enabled 81 | Enable HTTP REST API server for remote control 82 | HTTP server port 83 | Password 84 | Port 85 | Port, 1024 - 65535, default 8085 86 | Password to access the REST API 87 | Password 88 | REST API started... 89 | REST API stopped... 90 | Port invalid, use 8085 91 | Connection Info 92 | No IP! Tap to refresh. 93 | REST API Running 94 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 17 | 18 | 21 | 22 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/xml/preferences.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 14 | 15 | 21 | 22 | 23 | 24 | 29 | 30 | 34 | 35 | 39 | 40 | 44 | 48 | 49 | 55 | 63 | 64 | 71 | 72 | 76 | 77 | 82 | 83 | 84 | 85 | 93 | 101 | 102 | 103 | 104 | 105 | 114 | 122 | 123 | 127 | 128 | 133 | 138 | 139 | 140 | 141 | 145 | 146 | 151 | 152 | 159 | 160 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | plugins { 3 | id 'com.android.application' version '7.1.1' apply false 4 | id 'com.android.library' version '7.1.1' apply false 5 | } 6 | 7 | task clean(type: Delete) { 8 | delete rootProject.buildDir 9 | } -------------------------------------------------------------------------------- /fastlane/metadata/android/de-DE/changelogs/10.txt: -------------------------------------------------------------------------------- 1 | Erste Version -------------------------------------------------------------------------------- /fastlane/metadata/android/de-DE/changelogs/11.txt: -------------------------------------------------------------------------------- 1 | Verbesserte Einstellung für das Aufnahmeintervall 2 | Option für Blitz im JPG Modus -------------------------------------------------------------------------------- /fastlane/metadata/android/de-DE/changelogs/12.txt: -------------------------------------------------------------------------------- 1 | Fehlerbehebung für geplante Aufnahme -------------------------------------------------------------------------------- /fastlane/metadata/android/de-DE/changelogs/13.txt: -------------------------------------------------------------------------------- 1 | Neue Option: Bitrate der Videocodierung einstellen -------------------------------------------------------------------------------- /fastlane/metadata/android/de-DE/changelogs/14.txt: -------------------------------------------------------------------------------- 1 | Start/Stop/Vorschau in ActionBar verschoben -------------------------------------------------------------------------------- /fastlane/metadata/android/de-DE/changelogs/15.txt: -------------------------------------------------------------------------------- 1 | Update auf Android 13 SDK 2 | Unterstützung für monochromes Icon -------------------------------------------------------------------------------- /fastlane/metadata/android/de-DE/changelogs/16.txt: -------------------------------------------------------------------------------- 1 | Update auf Android 13 SDK 2 | Aufnahme stoppen, wenn Gerät ausgeschaltet wird -------------------------------------------------------------------------------- /fastlane/metadata/android/de-DE/changelogs/17.txt: -------------------------------------------------------------------------------- 1 | REST API 2 | Fernbedienung über http Schnittstelle 3 | (Danke an andreasb242) -------------------------------------------------------------------------------- /fastlane/metadata/android/de-DE/changelogs/18.txt: -------------------------------------------------------------------------------- 1 | Fehlerbehebung -------------------------------------------------------------------------------- /fastlane/metadata/android/de-DE/full_description.txt: -------------------------------------------------------------------------------- 1 | Minimalistische App, die Zeitraffervideos im Hintergrund bei ausgeschaltetem Bildschirm aufzeichnet. 2 | Zeitraffervideos können in verschiedenen Auflösungen gespeichert werden als MP4-Videodatei oder als einzelne JPEG-Dateien. 3 | Während der Aufnahme ist keine Vorschau sichtbar und der Bildschirm kann ausgeschaltet werden. 4 | Es gibt kein Aufnahmelimit, aber die App stoppt die Aufnahme, wenn der Akku Ladezustand oder der Speicherplatz niedrig ist. 5 | 6 | Funktionen: 7 | 8 | * Normales Video aufnehmen (MP4) 9 | * Zeitraffervideo aufnehmen (MP4/JPEG) 10 | * Wählen Sie verschiedene Auflösungen 11 | * Wählen Sie die zu verwendende Kamera aus 12 | * Planen Sie die Aufnahme an einem bestimmten Datum 13 | * Vorschau mit ausgewählter Auflösung 14 | * Verschlussgeräusch deaktivieren 15 | 16 | -------------------------------------------------------------------------------- /fastlane/metadata/android/de-DE/short_description.txt: -------------------------------------------------------------------------------- 1 | Minimalistische App, die Zeitraffervideos aufzeichnet -------------------------------------------------------------------------------- /fastlane/metadata/android/de-DE/title.txt: -------------------------------------------------------------------------------- 1 | TimeLapseCam -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/10.txt: -------------------------------------------------------------------------------- 1 | Initial version -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/11.txt: -------------------------------------------------------------------------------- 1 | Improved setting for capture interval 2 | Option to use flash in JPG mode -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/12.txt: -------------------------------------------------------------------------------- 1 | Bugfix for scheduled recording -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/13.txt: -------------------------------------------------------------------------------- 1 | Add option to set video encoding bitrate -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/14.txt: -------------------------------------------------------------------------------- 1 | Move start/stop/preview to action bar -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/15.txt: -------------------------------------------------------------------------------- 1 | Update for Android 13 SDK 2 | Monochrome icon support -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/16.txt: -------------------------------------------------------------------------------- 1 | Update for Android 14 SDK 2 | Stop recording when device is shutdown -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/17.txt: -------------------------------------------------------------------------------- 1 | REST API 2 | Remote control via http interface 3 | (Thanks to andreasb242) -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/18.txt: -------------------------------------------------------------------------------- 1 | Bugfix -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/19.txt: -------------------------------------------------------------------------------- 1 | Ignore battery optimizations 2 | Turkish translation -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/full_description.txt: -------------------------------------------------------------------------------- 1 | Minimalistic app that records time lapse videos in the background with the screen turned off. 2 | Time lapse videos can be saved in various resolutions as a MP4 video file, or as individual JPEG files. 3 | While recording there is no preview visible and the screen can be turned off. 4 | There is no recording limit, but the app will stop recording if battery level or disk space is low. 5 | 6 | Features: 7 | 8 | * Record normal video (MP4) 9 | * Record time lapse video (MP4/JPEG) 10 | * Select various resolutions 11 | * Select camera to use 12 | * Schedule recording on specific date 13 | * Preview with selected resolution 14 | * Disable shutter sound 15 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woheller69/TimeLapseCamera/dbec6562463db540398f4b3b4c1442e5fecfb0c3/fastlane/metadata/android/en-US/images/icon.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woheller69/TimeLapseCamera/dbec6562463db540398f4b3b4c1442e5fecfb0c3/fastlane/metadata/android/en-US/images/phoneScreenshots/01.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/short_description.txt: -------------------------------------------------------------------------------- 1 | Minimalistic app that records time lapse videos -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/title.txt: -------------------------------------------------------------------------------- 1 | TimeLapseCam -------------------------------------------------------------------------------- /fastlane/metadata/android/tr-TR/full_description.txt: -------------------------------------------------------------------------------- 1 | Ekran kapalıyken arka planda zaman aralıklı videolar kaydeden sade bir uygulama. 2 | Zaman aralıklı videolar çeşitli çözünürlüklerde MP4 video dosyası olarak veya ayrı JPEG dosyaları olarak kaydedilebilir. 3 | Kayıt sırasında ön izleme görünmez ve ekran kapatılabilir. 4 | Kayıt sınırı yoktur, ancak pil seviyesi veya disk alanı düşükse uygulama kaydı durduracaktır. 5 | 6 | Özellikler: 7 | 8 | * Normal video kaydedin (MP4) 9 | * Zaman aralıklı video kaydedin (MP4/JPEG) 10 | * Çeşitli çözünürlükleri seçin 11 | * Kullanılacak kamerayı seçin 12 | * Belirli bir tarihte kayıt planlayın 13 | * Seçilen çözünürlükle ön izleme yapın 14 | * Deklanşör sesini devre dışı bırakın 15 | -------------------------------------------------------------------------------- /fastlane/metadata/android/tr-TR/short_description.txt: -------------------------------------------------------------------------------- 1 | Zaman aralıklı videolar kaydeden sade uygulama 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/tr-TR/title.txt: -------------------------------------------------------------------------------- 1 | TimeLapseCam -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Enables namespacing of each library's R class so that its R class includes only the 19 | # resources declared in the library itself and none from the library's dependencies, 20 | # thereby reducing the size of the R class for that library 21 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woheller69/TimeLapseCamera/dbec6562463db540398f4b3b4c1442e5fecfb0c3/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Nov 15 12:47:39 CET 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip 4 | distributionSha256Sum=f581709a9c35e9cb92e16f585d2c4bc99b2b1a5f85d2badbd3dc6bff59e1e6dd 5 | distributionPath=wrapper/dists 6 | zipStorePath=wrapper/dists 7 | zipStoreBase=GRADLE_USER_HOME 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | } 14 | } 15 | rootProject.name = "TimeLapse" 16 | include ':app' 17 | --------------------------------------------------------------------------------