diff --git a/.classpath b/.classpath
deleted file mode 100644
index dfadf67..0000000
--- a/.classpath
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/.project b/.project
deleted file mode 100644
index 84d7c45..0000000
--- a/.project
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
- FakeDawn
-
-
-
-
-
- com.android.ide.eclipse.adt.ResourceManagerBuilder
-
-
-
-
- com.android.ide.eclipse.adt.PreCompilerBuilder
-
-
-
-
- org.eclipse.jdt.core.javabuilder
-
-
-
-
- com.android.ide.eclipse.adt.ApkBuilder
-
-
-
-
-
- com.android.ide.eclipse.adt.AndroidNature
- org.eclipse.jdt.core.javanature
-
-
diff --git a/.settings/org.eclipse.jdt.ui.prefs b/.settings/org.eclipse.jdt.ui.prefs
deleted file mode 100644
index dea482e..0000000
--- a/.settings/org.eclipse.jdt.ui.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-eclipse.preferences.version=1
-org.eclipse.jdt.ui.javadoc=false
-org.eclipse.jdt.ui.text.custom_code_templates=/**\n * @return the ${bare_field_name}\n *//**\n * @param ${param} the ${bare_field_name} to set\n *//**\n * ${tags}\n *//**\n * Copyright 2012 Francesco Balducci\n *\n * This file is part of FakeDawn.\n *\n * FakeDawn is free software\: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * FakeDawn is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with FakeDawn. If not, see <http\://www.gnu.org/licenses/>.\n *//**\n * @author ${user}\n *\n * ${tags}\n *//**\n * \n *//**\n * ${tags}\n *//* (non-Javadoc)\n * ${see_to_overridden}\n *//**\n * ${tags}\n * ${see_to_target}\n */${filecomment}\n${package_declaration}\n\n${typecomment}\n${type_declaration}\n\n\n\n// ${todo} Auto-generated catch block\n${exception_var}.printStackTrace();// ${todo} Auto-generated method stub\n${body_statement}${body_statement}\n// ${todo} Auto-generated constructor stubreturn ${field};${field} \= ${param};
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
deleted file mode 100644
index 0898327..0000000
--- a/AndroidManifest.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/README.md b/README.md
index 2462a65..6ec0edb 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,7 @@ FakeDawn
Android app: an artificial dawn alarm to help you wake up gently in the morning.
Copyright (C) 2012 Francesco Balducci
+Copyright 2026 Olivier Vialatte
Copying and distribution of this file, with or without modification,
are permitted in any medium without royalty provided the copyright
@@ -12,4 +13,4 @@ Copyright (C) 2012 Francesco Balducci
Primary repository: https://github.com/balau/FakeDawn
FakeDawn is an open source project. See the file COPYING for copying conditions.
-
\ No newline at end of file
+
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..472e034
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,40 @@
+plugins {
+ id 'com.android.application'
+}
+
+android {
+ namespace 'org.balau.fakedawn'
+ compileSdk 34
+
+ defaultConfig {
+ applicationId "org.balau.fakedawn"
+ minSdk 21 // On monte à 21 (Android 5.0) pour la compatibilité moderne
+ targetSdk 34
+ versionCode 5
+ versionName "3.5"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_17
+ targetCompatibility JavaVersion.VERSION_17
+ }
+}
+
+dependencies {
+ // Bibliothèques de compatibilité modernes (AndroidX)
+ implementation 'androidx.appcompat:appcompat:1.6.1'
+ implementation 'com.google.android.material:material:1.11.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+
+ // Pour les préférences (très utilisé dans les vieux projets)
+ implementation 'androidx.preference:preference:1.2.1'
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..16f9582
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/org/balau/fakedawn/Alarm.java b/app/src/main/java/org/balau/fakedawn/Alarm.java
similarity index 55%
rename from src/org/balau/fakedawn/Alarm.java
rename to app/src/main/java/org/balau/fakedawn/Alarm.java
index b8886b7..a9c5cc9 100644
--- a/src/org/balau/fakedawn/Alarm.java
+++ b/app/src/main/java/org/balau/fakedawn/Alarm.java
@@ -1,5 +1,6 @@
/**
* Copyright 2012 Francesco Balducci
+ * Copyright 2026 Olivier Vialatte
*
* This file is part of FakeDawn.
*
@@ -24,17 +25,24 @@
import java.util.Calendar;
import android.app.AlarmManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.content.SharedPreferences;
+import android.os.Build;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;
+import androidx.core.app.NotificationCompat;
+import android.content.pm.ServiceInfo;
public class Alarm extends Service {
public static final String EXTRA_SHOW_TOAST = "org.balau.fakedawn.Alarm.EXTRA_SHOW_TOAST";
+ private static final int NOTIFICATION_ID = 1;
private static final long TOLERANCE_MILLIS = 1000*10;
@Override
@@ -48,6 +56,30 @@ public IBinder onBind(Intent arg0) {
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
+ // 1. Créer le canal de notification pour Android 8+
+ /* String CHANNEL_ID = "fakedawn_service";
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
+ "Alarme FakeDawn", NotificationManager.IMPORTANCE_LOW);
+ getSystemService(NotificationManager.class).createNotificationChannel(channel);
+ }
+
+ // 2. Lancer en premier plan (Foreground) pour éviter le crash immédiat
+ Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
+ .setContentTitle("FakeDawn")
+ .setContentText("Mise à jour de l'alarme...")
+ .setSmallIcon(android.R.drawable.ic_lock_idle_alarm)
+ .build();
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ startForeground(
+ NOTIFICATION_ID,
+ notification,
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE // Matches the manifest
+ );
+ } else {
+ startForeground(NOTIFICATION_ID, notification);
+ } */
boolean showToast;
if(intent != null)
@@ -66,17 +98,21 @@ public int onStartCommand(Intent intent, int flags, int startId) {
Calendar nextAlarmTime = getNextAlarmTime();
if (nextAlarmTime == null)
{
- message = "No week day selected! Fake Dawn Alarm Disabled.";
+ message = getString(R.string.No_week_day_selected) + getString(R.string.app_name) + getString(R.string.alarm_disabled);
}
else
{
set(nextAlarmTime);
message = nextAlarmMessage(nextAlarmTime);
+ java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("E dd/MM HH:mm", java.util.Locale.getDefault());
+ String timeLabel = sdf.format(nextAlarmTime.getTime());
+ showNotification(timeLabel);
+ return START_STICKY;
}
}
else
{
- message = "Fake Dawn Alarm Disabled.";
+ message = getString(R.string.app_name) + getString(R.string.alarm_disabled);
}
Log.d("FakeDawn", message);
if(showToast)
@@ -84,18 +120,62 @@ public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
}
// If we get killed, after returning from here, restart
+ stopForeground(true);
return START_STICKY;
}
-
+
+ private void showNotification(String nextAlarmStr) {
+ // 1. Créer le canal de notification pour Android 8+
+ NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ String CHANNEL_ID = "fakedawn_service";
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
+ getString(R.string.alarme_fakeDawn), NotificationManager.IMPORTANCE_LOW);
+ nm.createNotificationChannel(channel);
+ }
+
+ Intent intent = new Intent(this, Preferences.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+
+ int flags = PendingIntent.FLAG_UPDATE_CURRENT;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ flags |= PendingIntent.FLAG_IMMUTABLE;
+ }
+ PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, flags);
+
+ // 2. Lancer en premier plan (Foreground) pour éviter le crash immédiat
+ NotificationCompat.Builder notification = new NotificationCompat.Builder(this, CHANNEL_ID)
+ .setContentTitle(getString(R.string.fakedawn_activated))
+ .setContentText(getString(R.string.next_alarm) + nextAlarmStr)
+ .setSmallIcon(android.R.drawable.ic_lock_idle_alarm)
+ .setOngoing(true)
+ .setContentIntent(pendingIntent)
+ .setPriority(NotificationCompat.PRIORITY_LOW);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ startForeground(NOTIFICATION_ID, notification.build(),
+ android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE);
+ } else {
+ startForeground(NOTIFICATION_ID, notification.build());
+ }
+ }
+
private PendingIntent getOpenDawnPendingIntent()
{
- Intent openDawn = new Intent(AlarmReceiver.ACTION_START_ALARM);
- PendingIntent openDawnPendingIntent = PendingIntent.getBroadcast(
- getApplicationContext(),
- 0,
+ Intent openDawn = new Intent(this, AlarmReceiver.class);
+ openDawn.setAction(AlarmReceiver.ACTION_START_ALARM);
+
+ int flags = PendingIntent.FLAG_UPDATE_CURRENT;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ flags |= PendingIntent.FLAG_IMMUTABLE; // Obligatoire sur Android 12+
+ }
+
+ return PendingIntent.getBroadcast(
+ getApplicationContext(),
+ 0,
openDawn,
- 0);
- return openDawnPendingIntent;
+ flags);
}
private AlarmManager getAlarmManager()
@@ -178,37 +258,42 @@ private Calendar getNextAlarmTime()
return nextAlarmTime;
}
+ private void set(Calendar nextAlarmTime) {
+ AlarmManager alarmManager = getAlarmManager();
+ PendingIntent openDawnIntent = getOpenDawnPendingIntent();
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP,
+ nextAlarmTime.getTimeInMillis(), openDawnIntent);
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ alarmManager.setExact(AlarmManager.RTC_WAKEUP,
+ nextAlarmTime.getTimeInMillis(), openDawnIntent);
+ } else {
+ alarmManager.set(AlarmManager.RTC_WAKEUP,
+ nextAlarmTime.getTimeInMillis(), openDawnIntent);
+ }
+ }
+
private void set(AlarmManager alarmManager, int type, long triggerAtMillis, PendingIntent operation)
{
- // API 19 changed set() behaviour and added setExact
+ // API 19 changed set() behaviour and added setExact
// https://developer.android.com/reference/android/app/AlarmManager.html#set(int, long, android.app.PendingIntent)
// Using setExact if it exists, otherwise fall back to set.
- try {
- Method setExact = AlarmManager.class.getDeclaredMethod(
- "setExact", int.class, long.class, PendingIntent.class);
- setExact.invoke(alarmManager, type,
- triggerAtMillis, operation);
- } catch (NoSuchMethodException e) {
- alarmManager.set(type,
- triggerAtMillis, operation);
- } catch (IllegalAccessException e) {
- throw new RuntimeException(e);
- } catch (IllegalArgumentException e) {
- throw new RuntimeException(e);
- } catch (InvocationTargetException e) {
- throw new RuntimeException(e);
- }
- }
-
- private void set(Calendar nextAlarmTime)
- {
- AlarmManager alarmManager = getAlarmManager();
- PendingIntent openDawnIntent = getOpenDawnPendingIntent();
- set(
- alarmManager,
- AlarmManager.RTC_WAKEUP,
- nextAlarmTime.getTimeInMillis(),
- openDawnIntent);
+ try {
+ Method setExact = AlarmManager.class.getDeclaredMethod(
+ "setExact", int.class, long.class, PendingIntent.class);
+ setExact.invoke(alarmManager, type,
+ triggerAtMillis, operation);
+ } catch (NoSuchMethodException e) {
+ alarmManager.set(type,
+ triggerAtMillis, operation);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ } catch (IllegalArgumentException e) {
+ throw new RuntimeException(e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
}
private String getPlural(long n, String name)
diff --git a/src/org/balau/fakedawn/AlarmReceiver.java b/app/src/main/java/org/balau/fakedawn/AlarmReceiver.java
similarity index 59%
rename from src/org/balau/fakedawn/AlarmReceiver.java
rename to app/src/main/java/org/balau/fakedawn/AlarmReceiver.java
index a5a5d7e..9045a25 100644
--- a/src/org/balau/fakedawn/AlarmReceiver.java
+++ b/app/src/main/java/org/balau/fakedawn/AlarmReceiver.java
@@ -1,5 +1,6 @@
/**
* Copyright 2012 Francesco Balducci
+ * Copyright 2026 Olivier Vialatte
*
* This file is part of FakeDawn.
*
@@ -20,14 +21,21 @@
import java.util.Calendar;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.content.SharedPreferences;
+// import android.content.SharedPreferences;
+import android.os.Build;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.util.Log;
+import androidx.core.app.NotificationCompat;
+
/**
* @author francesco
*
@@ -66,7 +74,7 @@ private void releaseWakeLock(boolean expectedHeld) {
}
}
- public static Calendar getAlarmStart(SharedPreferences pref)
+ /* public static Calendar getAlarmStart(SharedPreferences pref)
{
Calendar rightNow = Calendar.getInstance();
@@ -90,7 +98,7 @@ else if(alarmStartMillis - rightNowMillis < -halfDayMillis)
}
return alarmStart;
- }
+ } */
/* (non-Javadoc)
* @see android.content.BroadcastReceiver#onReceive(android.content.Context, android.content.Intent)
@@ -111,13 +119,63 @@ public void onReceive(Context context, Intent intent) {
PowerManager.PARTIAL_WAKE_LOCK,
"FakeDawn.AlarmReceiver");
AlarmReceiver.m_alarmWakeLock.acquire(WAKE_LOCK_TIMEOUT_MILLIS); //TODO: use WakefulBroadcastReceiver instead?
- Intent openDawn = new Intent(context, Dawn.class);
+
+ // Il faudra peut-être modifier ci-après
+ /* Intent openDawn = new Intent(context, Dawn.class);
openDawn.setFlags(
Intent.FLAG_ACTIVITY_NEW_TASK|
Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS|
Intent.FLAG_FROM_BACKGROUND);
- Log.d("FakeDawn", "Starting Dawn Activity.");
- context.startActivity(openDawn);
+ Log.d("FakeDawn", "Starting Dawn Activity."); */
+ // 1. Créer l'intention pour l'écran de réveil
+ Intent dawnIntent = new Intent(context, Dawn.class);
+ dawnIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_NO_USER_ACTION
+ | Intent.FLAG_ACTIVITY_SINGLE_TOP
+ | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+
+ // 2. Créer le PendingIntent (obligatoire pour la notification)
+ int flags = PendingIntent.FLAG_UPDATE_CURRENT;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ flags |= PendingIntent.FLAG_IMMUTABLE;
+ }
+ PendingIntent fullScreenPendingIntent = PendingIntent.getActivity(context, 0, dawnIntent, flags);
+
+ // 3. Créer la notification de réveil
+ String CHANNEL_ID = "fakedawn_alarm";
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
+ .setSmallIcon(android.R.drawable.ic_lock_idle_alarm)
+ .setContentTitle(context.getString(R.string.fakedawn_clock))
+ .setContentText(context.getString(R.string.alarm_started))
+ .setPriority(NotificationCompat.PRIORITY_MAX)
+ .setCategory(NotificationCompat.CATEGORY_ALARM)
+ .setFullScreenIntent(fullScreenPendingIntent, true);
+
+ // 4. Envoyer la notification
+ NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ // Identifiant du canal (doit correspondre à celui utilisé dans le Builder)
+
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
+ // On vérifie si le canal existe déjà
+ if (notificationManager.getNotificationChannel(CHANNEL_ID) == null) {
+ NotificationChannel channel = new NotificationChannel(
+ CHANNEL_ID,
+ "Réveil FakeDawn",
+ NotificationManager.IMPORTANCE_HIGH // Crucial pour le plein écran
+ );
+ channel.setDescription("Utilisé pour afficher l'écran de réveil");
+ channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
+
+ // Optionnel : Désactiver le vibreur/son par défaut de la notification
+ // car l'activité Dawn s'en occupe déjà
+ channel.enableVibration(false);
+ channel.setSound(null, null);
+
+ notificationManager.createNotificationChannel(channel);
+ }
+ }
+ notificationManager.notify(2, builder.build());
+ context.startActivity(dawnIntent);
//TODO: start sound service?
}
else if(intent.getAction().equals(ACTION_STOP_ALARM))
diff --git a/src/org/balau/fakedawn/ColorPickerDialog.java b/app/src/main/java/org/balau/fakedawn/ColorPickerDialog.java
similarity index 100%
rename from src/org/balau/fakedawn/ColorPickerDialog.java
rename to app/src/main/java/org/balau/fakedawn/ColorPickerDialog.java
diff --git a/app/src/main/java/org/balau/fakedawn/Dawn.java b/app/src/main/java/org/balau/fakedawn/Dawn.java
new file mode 100644
index 0000000..ccc8fb5
--- /dev/null
+++ b/app/src/main/java/org/balau/fakedawn/Dawn.java
@@ -0,0 +1,270 @@
+/**
+ * Copyright 2012 Francesco Balducci
+ * Copyright 2026 Olivier Vialatte
+ *
+ * This file is part of FakeDawn.
+ *
+ * FakeDawn is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FakeDawn is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with FakeDawn. If not, see .
+ */
+package org.balau.fakedawn;
+
+import android.animation.ArgbEvaluator;
+import android.animation.ValueAnimator;
+import android.app.Activity;
+import android.app.NotificationManager;
+import android.content.SharedPreferences;
+import android.graphics.Color;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+// import android.widget.TextView;
+import android.media.AudioAttributes;
+import android.media.MediaPlayer;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+// import android.util.Log;
+import java.io.IOException;
+import android.os.Vibrator;
+
+public class Dawn extends Activity {
+ private boolean useDismissButton;
+ private ValueAnimator colorAnimation;
+ private MediaPlayer mediaPlayer;
+ private final Handler soundHandler = new Handler(Looper.getMainLooper());
+ private Runnable stopSoundRunnable;
+ private Vibrator vibrator;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ // 1. Luminosité au minimum immédiatement
+ WindowManager.LayoutParams lp = getWindow().getAttributes();
+ lp.screenBrightness = 0.01f;
+ getWindow().setAttributes(lp);
+
+ // 1. Gestion du réveil de l'écran (Indispensable pour Android 14)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
+ setShowWhenLocked(true);
+ setTurnScreenOn(true);
+ }
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
+ WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD |
+ WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED |
+ WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
+
+ // 2. Passage en plein écran (cache les barres système)
+ getWindow().getDecorView().setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.predawn);
+ NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ if (nm != null) nm.cancel(2); // AlarmReceiver.NOTIFICATION_ALARM_RUNNING_ID);
+ SharedPreferences pref = getSharedPreferences("main", MODE_PRIVATE);
+ useDismissButton = pref.getBoolean("dismiss", false);
+ View root = findViewById(R.id.predawn_root);
+ root.setOnClickListener(v -> {
+ if (!useDismissButton) {
+ // Méthode "Touch Screen" : on arrête tout
+ stopAlarm();
+ } else {
+ // Méthode "Dismiss Button" : on bascule vers la vue normale
+ showDawnWithButton();
+ }
+ });
+ soundHandler.postDelayed(() -> {
+ boolean soundEnabled = pref.getBoolean("sound_master_enabled", true);
+
+ if (soundEnabled) {
+ startAlarmSound(); // Lance la musique
+ boolean vibrateEnabled = pref.getBoolean("vibrate", false); // Clé de votre ToggleButton
+ if (vibrateEnabled) {
+ vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
+ if (vibrator != null && vibrator.hasVibrator()) {
+ // Vibration : 0ms pause, 500ms vibre, 500ms pause... (en boucle)
+ long[] pattern = {0, 1000, 10000};
+ vibrator.vibrate(pattern, 0); // 0 = répéter indéfiniment
+ }
+ }
+ // 2. Programmation de l'ARRÊT automatique du son
+ stopSoundRunnable = () -> {
+ if (mediaPlayer != null) {
+ if (mediaPlayer.isPlaying()) mediaPlayer.stop();
+ mediaPlayer.release();
+ mediaPlayer = null;
+ // Log.d("FakeDawn", "Sound stopped automatically after duration limit.");
+ if (vibrator != null) {
+ vibrator.cancel();
+ }
+ }
+ };
+ soundHandler.postDelayed(stopSoundRunnable, pref.getInt("sound_duration", 15) * 60 * 1000L);
+ }
+ }, pref.getInt("light_pre_delay", 15) * 60 * 1000L);
+ // if (nm != null) nm.cancel(2); // AlarmReceiver.NOTIFICATION_ALARM_RUNNING_ID);
+ startColorAnimation(root);
+ }
+
+ private void startAlarmSound() {
+ SharedPreferences pref = getSharedPreferences("main", MODE_PRIVATE);
+ String soundStr = pref.getString("sound", "");
+
+ if (soundStr.isEmpty()) {
+ // Log.d("FakeDawn", "No sound selected (Silent)");
+ return;
+ }
+
+ try {
+ mediaPlayer = new MediaPlayer();
+ mediaPlayer.setDataSource(this, Uri.parse(soundStr));
+
+ // Configuration pour que le son sorte comme une alarme (même si le téléphone est en vibreur)
+ mediaPlayer.setAudioAttributes(new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_ALARM)
+ .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
+ .build());
+
+ mediaPlayer.setLooping(true); // La musique boucle jusqu'à l'arrêt
+ mediaPlayer.prepare();
+ mediaPlayer.start();
+
+ // Log.d("FakeDawn", "Alarm sound started");
+ } catch (IOException e) {
+ // Log.e("FakeDawn", "Error playing sound: " + e.getMessage());
+ }
+ }
+
+ private void showDawnWithButton() {
+ if (colorAnimation != null) colorAnimation.cancel();
+ // Affiche le layout avec le bouton
+ setContentView(R.layout.dawn);
+
+ // Rétablir la luminosité normale (système)
+ WindowManager.LayoutParams lp = getWindow().getAttributes();
+ lp.screenBrightness = -1f; // -1 = Valeur par défaut de l'utilisateur
+ getWindow().setAttributes(lp);
+ final View background = findViewById(R.id.dawn_background);
+
+ // Configurer le bouton Dismiss de la nouvelle vue
+ Button dismissBtn = findViewById(R.id.dismiss_button);
+ if (dismissBtn != null) {
+ dismissBtn.setOnClickListener(v -> stopAlarm());
+ }
+
+ // Lancer l'animation de couleur si vous en avez une
+ startColorAnimation(background);
+ }
+
+ private void startColorAnimation(View background) {
+ // 5. Calcul de la durée de transition
+ SharedPreferences pref = getSharedPreferences("main", MODE_PRIVATE);
+ // On récupère la durée : light_max (fin) - light_start (début)
+ int durationMinutes = pref.getInt("light_pre_delay", 15) + pref.getInt("light_post_delay", 15);
+ if (durationMinutes <= 0) durationMinutes = 1; // Sécurité
+ long durationMillis = durationMinutes * 60 * 1000;
+
+ // 6. Animation du fond (Noir vers Blanc)
+ colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), Color.BLACK, Color.WHITE);
+ colorAnimation.setDuration(durationMillis);
+
+ colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animator) {
+ int color = (int) animator.getAnimatedValue();
+ background.setBackgroundColor(color);
+
+ // Optionnel : On peut aussi ajuster la luminosité physique de l'écran
+ WindowManager.LayoutParams lp = getWindow().getAttributes();
+ if (lp.screenBrightness < 1.0f && lp.screenBrightness != -1f) {
+ lp.screenBrightness = animator.getAnimatedFraction();
+ getWindow().setAttributes(lp);
+ }
+ }
+ });
+
+ colorAnimation.start();
+ }
+ private void stopAlarm() {
+ soundHandler.removeCallbacksAndMessages(null);
+ if (colorAnimation != null) colorAnimation.cancel();
+ if (vibrator != null) {
+ vibrator.cancel();
+ }
+ if (mediaPlayer != null) {
+ try {
+ if (mediaPlayer.isPlaying()) mediaPlayer.stop();
+ } catch (Exception e) {
+ mediaPlayer.release();
+ mediaPlayer = null;
+ }
+ }
+ // 2. EFFACER LA NOTIFICATION "En cours"
+ NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ if (nm != null) nm.cancel(2); // ID 2 correspond à celui d'AlarmReceiver
+
+ // 3. Fermer l'activité
+ finish();
+ }
+}
+
+
+ /*
+ // 3. Récupération des vues
+ final View background = findViewById(R.id.dawn_background);
+ Button dismissButton = findViewById(R.id.dismiss_button);
+ // Note: DigitalClock est déprécié mais fonctionne encore si vous l'utilisez dans le XML
+
+ // 4. Action du bouton Dismiss
+ dismissButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Arrêter l'activité (et donc l'alarme visuelle)
+ finish();
+ }
+ });
+
+ // 5. Calcul de la durée de transition
+ SharedPreferences pref = getSharedPreferences("main", MODE_PRIVATE);
+ // On récupère la durée : light_max (fin) - light_start (début)
+ int durationMinutes = pref.getInt("light_max", 30) - pref.getInt("light_start", 0);
+ if (durationMinutes <= 0) durationMinutes = 1; // Sécurité
+ long durationMillis = durationMinutes * 60 * 1000;
+
+ // 6. Animation du fond (Noir vers Blanc)
+ ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), Color.BLACK, Color.WHITE);
+ colorAnimation.setDuration(durationMillis);
+
+ colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animator) {
+ int color = (int) animator.getAnimatedValue();
+ background.setBackgroundColor(color);
+
+ // Optionnel : On peut aussi ajuster la luminosité physique de l'écran
+ WindowManager.LayoutParams lp = getWindow().getAttributes();
+ lp.screenBrightness = animator.getAnimatedFraction(); // de 0.0 à 1.0
+ getWindow().setAttributes(lp);
+ }
+ });
+
+ colorAnimation.start();
+ }
+}*/
\ No newline at end of file
diff --git a/src/org/balau/fakedawn/DawnSound.java b/app/src/main/java/org/balau/fakedawn/DawnSound.java
similarity index 100%
rename from src/org/balau/fakedawn/DawnSound.java
rename to app/src/main/java/org/balau/fakedawn/DawnSound.java
diff --git a/src/org/balau/fakedawn/InstallationReceiver.java b/app/src/main/java/org/balau/fakedawn/InstallationReceiver.java
similarity index 100%
rename from src/org/balau/fakedawn/InstallationReceiver.java
rename to app/src/main/java/org/balau/fakedawn/InstallationReceiver.java
diff --git a/src/org/balau/fakedawn/IntervalSlider.java b/app/src/main/java/org/balau/fakedawn/IntervalSlider.java
similarity index 100%
rename from src/org/balau/fakedawn/IntervalSlider.java
rename to app/src/main/java/org/balau/fakedawn/IntervalSlider.java
diff --git a/src/org/balau/fakedawn/License.java b/app/src/main/java/org/balau/fakedawn/License.java
similarity index 100%
rename from src/org/balau/fakedawn/License.java
rename to app/src/main/java/org/balau/fakedawn/License.java
diff --git a/app/src/main/java/org/balau/fakedawn/Preferences.java b/app/src/main/java/org/balau/fakedawn/Preferences.java
new file mode 100644
index 0000000..3ea0f66
--- /dev/null
+++ b/app/src/main/java/org/balau/fakedawn/Preferences.java
@@ -0,0 +1,524 @@
+/**
+ * Copyright 2012 Francesco Balducci
+ * Copyright 2026 Olivier Vialatte
+ *
+ * This file is part of FakeDawn.
+ *
+ * FakeDawn is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FakeDawn is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with FakeDawn. If not, see .
+ */
+
+package org.balau.fakedawn;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.balau.fakedawn.ColorPickerDialog.OnColorChangedListener;
+import org.balau.fakedawn.TimeSlider.OnTimesChangedListener; // Import décommenté
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.TimePickerDialog;
+import android.app.TimePickerDialog.OnTimeSetListener;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.media.AudioManager;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.LinearLayout;
+import android.widget.NumberPicker;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+import android.widget.TextView;
+import android.widget.TimePicker;
+import android.widget.Toast;
+import android.widget.ToggleButton;
+
+public class Preferences extends Activity implements OnClickListener, OnSeekBarChangeListener, OnColorChangedListener, OnTimeSetListener, OnTimesChangedListener {
+
+ // Variables pour ta nouvelle logique
+ private int lightStartH = 7, lightStartM = 0;
+ private int lightPreDelayMinutes = 15;
+ private int lightPostDelayMinutes = 15;
+ private int lightDuration = lightPreDelayMinutes + lightPostDelayMinutes;
+ private int soundStartH = 7, soundStartM = 15;
+ private CheckBox m_checkBoxSoundEnabled;
+ private LinearLayout m_containerSoundOptions, m_containerAlarmHours, m_containerAlarmDays, m_containerSoundEnable;
+ private ToggleButton m_toggleButtonAlarmEnabled, m_toggleButtonUseDismiss;
+ private static final int REQUEST_PICK_SOUND = 0;
+ private Uri m_soundUri;
+ private HelpListener m_helpListener = new HelpListener();
+
+ private TextView txtLightStart, txtLightPreDelay, txtLightPostDelay, txtSoundStart, textDismissMethod;
+
+ private void updateLabels() {
+ if (txtLightStart != null) txtLightStart.setText(String.format("%02d:%02d", lightStartH, lightStartM));
+ if (txtLightPreDelay != null) txtLightPreDelay.setText(lightPreDelayMinutes + " min");
+ if (txtLightPostDelay != null) txtLightPostDelay.setText(lightPostDelayMinutes + " min");
+ if (txtSoundStart != null) txtSoundStart.setText(String.format("%02d:%02d", soundStartH, soundStartM));
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.preferences);
+
+ txtLightStart = findViewById(R.id.txtLightStart);
+ txtLightPreDelay = findViewById(R.id.txtLightPreDelay);
+ txtLightPostDelay = findViewById(R.id.txtLightPostDelay);
+ txtSoundStart = findViewById(R.id.txtSoundStart);
+ m_toggleButtonUseDismiss = findViewById(R.id.toggleButtonUseDismiss);
+ textDismissMethod = findViewById(R.id.textDismissMethod);
+ m_checkBoxSoundEnabled = findViewById(R.id.checkBoxSoundEnabled);
+ m_containerSoundEnable = findViewById(R.id.containerSoundEnable);
+ m_containerSoundOptions = findViewById(R.id.containerSoundOptions);
+ m_containerAlarmHours = findViewById(R.id.containerAlarmHours);
+ m_containerAlarmDays = findViewById(R.id.containerAlarmDays);
+ m_toggleButtonAlarmEnabled = findViewById(R.id.toggleButtonAlarmEnabled);
+
+ m_checkBoxSoundEnabled.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ // Active/Désactive le container et le texte de durée
+ updateSoundUI(isChecked);
+ });
+ if(m_toggleButtonAlarmEnabled != null) m_toggleButtonAlarmEnabled.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ updateGlobalUI(isChecked);
+ });
+ // Liaison des clics
+ /*if(txtLightStart != null) txtLightStart.setOnClickListener(this);
+ if(txtLightPreDelay != null) txtLightPreDelay.setOnClickListener(this);
+ if(txtLightPostDelay != null) txtLightPostDelay.setOnClickListener(this);
+ if(txtSoundStart != null) txtSoundStart.setOnClickListener(this);
+ if(txtSoundDuration != null) txtSoundDuration.setOnClickListener(this); */
+ if(txtLightStart != null) txtLightStart.setOnClickListener(v -> {
+ showTimePicker(lightStartH, lightStartM, (view, hour, minute) -> {
+ lightStartH = hour;
+ lightStartM = minute;
+ // Formule : Sound = Light + PreDelay
+ lightStartH = (soundStartH * 60 + soundStartM - lightPreDelayMinutes) % 1440 / 60;
+ lightStartM = (soundStartH * 60 + soundStartM - lightPreDelayMinutes) % 1440 % 60;
+ updateLabels();
+ });
+ });
+ if(txtLightPreDelay != null) txtLightPreDelay.setOnClickListener(v -> {
+ showDurationPicker(lightPreDelayMinutes, (newDelay) -> {
+ lightPreDelayMinutes = newDelay;
+ // Formule : Sound = Light + PreDelay
+ int totalLightMinutes = lightStartH * 60 + lightStartM;
+ int totalSoundMinutes = (totalLightMinutes + lightPreDelayMinutes) % 1440;
+
+ soundStartH = totalSoundMinutes / 60;
+ soundStartM = totalSoundMinutes % 60;
+ lightDuration = lightPreDelayMinutes + lightPostDelayMinutes;
+ updateLabels();
+ });
+ });
+ if(txtLightPostDelay != null) txtLightPostDelay.setOnClickListener(v -> {
+ showDurationPicker(lightPostDelayMinutes, (newDelay) -> {
+ lightPostDelayMinutes = newDelay;
+ lightDuration = lightPreDelayMinutes + lightPostDelayMinutes;
+ updateLabels();
+ });
+ });
+ if(txtSoundStart != null) txtSoundStart.setOnClickListener(v -> {
+ showTimePicker(soundStartH, soundStartM, (view, hour, minute) -> {
+ soundStartH = hour;
+ soundStartM = minute;
+ // Logic: Light = Sound - PreDelay
+ int totalMinutes = (soundStartH * 60 + soundStartM) - lightPreDelayMinutes;
+ if (totalMinutes < 0) totalMinutes += 1440;
+ lightStartH = totalMinutes / 60;
+ lightStartM = totalMinutes % 60;
+ updateLabels();
+ });
+ });
+
+ // Liaison des boutons standards
+ findViewById(R.id.buttonSave).setOnClickListener(this);
+ findViewById(R.id.buttonDiscard).setOnClickListener(this);
+ findViewById(R.id.buttonSound).setOnClickListener(this);
+
+ SeekBar seekBarVolume = (SeekBar) findViewById(R.id.seekBarVolume);
+ seekBarVolume.setOnSeekBarChangeListener(this);
+
+ loadPreferences();
+ }
+
+ private void updateSoundUI(boolean enabled) {
+ // Grise ou active les composants
+ m_containerSoundOptions.setEnabled(enabled);
+ findViewById(R.id.buttonSound).setEnabled(enabled);
+ findViewById(R.id.seekBarVolume).setEnabled(enabled);
+ findViewById(R.id.toggleButtonVibrate).setEnabled(enabled);
+
+ // Change l'opacité pour l'effet visuel "grisé"
+ float alpha = enabled ? 1.0f : 0.4f;
+ m_containerSoundOptions.setAlpha(alpha);
+ }
+
+ private void updateGlobalUI(boolean enabled) {
+ // Liste des vues à griser/désactiver
+ View[] viewsToToggle = {
+ m_containerAlarmHours, m_containerAlarmDays,txtSoundStart,
+ m_containerSoundOptions, m_containerSoundEnable, textDismissMethod, m_toggleButtonUseDismiss
+ };
+
+ float alpha = enabled ? 1.0f : 0.4f;
+ for (View v : viewsToToggle) {
+ if (v != null) {
+ v.setAlpha(alpha);
+ setRecursiveEnabled(v, enabled);
+ }
+ }
+
+ // Si l'alarme est activée, on respecte aussi l'état de la checkbox Sound
+ if (enabled) {
+ updateSoundUI(m_checkBoxSoundEnabled.isChecked());
+ }
+ }
+
+ private void setRecursiveEnabled(View view, boolean enabled) {
+ view.setEnabled(enabled);
+ if (view instanceof ViewGroup) {
+ ViewGroup group = (ViewGroup) view;
+ for (int i = 0; i < group.getChildCount(); i++) {
+ setRecursiveEnabled(group.getChildAt(i), enabled);
+ }
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ // On vérifie que c'est bien le retour du sélecteur de son (REQUEST_PICK_SOUND est 0 chez vous)
+ if (resultCode == RESULT_OK && requestCode == REQUEST_PICK_SOUND) {
+ Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
+
+ // On met à jour la variable de classe
+ m_soundUri = uri;
+
+ // On met à jour le texte du bouton immédiatement
+ updateSoundButtonText();
+ }
+ }
+
+ private void updateSoundButtonText() {
+ Button buttonSound = findViewById(R.id.buttonSound);
+ if (buttonSound == null) return;
+
+ if (m_soundUri != null) {
+ // On récupère le titre de la sonnerie via le RingtoneManager
+ android.media.Ringtone ringtone = RingtoneManager.getRingtone(this, m_soundUri);
+ if (ringtone != null) {
+ buttonSound.setText(ringtone.getTitle(this));
+ } else {
+ buttonSound.setText(getString(R.string.sound_selected));
+ }
+ } else {
+ buttonSound.setText(getString(R.string.Silent));
+ }
+ }
+
+ private void loadPreferences() {
+ SharedPreferences pref = getSharedPreferences("main", MODE_PRIVATE);
+ int startH = pref.getInt("dawn_start_hour", 7);
+ int startM = pref.getInt("dawn_start_minute", 0);
+ int globalStart = startH * 60 + startM;
+
+ // Calcul des heures à partir des offsets enregistrés
+ int lStart = globalStart; // + pref.getInt("light_start", 0);
+ lightStartH = lStart / 60;
+ lightStartM = lStart % 60;
+ lightPreDelayMinutes = pref.getInt("light_pre_delay", 15);
+ lightPostDelayMinutes = pref.getInt("light_post_delay", 15);
+ lightDuration = lightPreDelayMinutes + lightPostDelayMinutes; // pref.getInt("light_max", 30) - pref.getInt("light_start", 0);
+
+ int sStart = globalStart + lightPreDelayMinutes; // globalStart + pref.getInt("sound_start", 15);
+ soundStartH = sStart / 60;
+ soundStartM = sStart % 60;
+
+ // --- 2. État Global et Volume ---
+ ToggleButton alarmEnabled = findViewById(R.id.toggleButtonAlarmEnabled);
+ if (alarmEnabled != null) {
+ boolean isEnabled = pref.getBoolean("enabled", true);
+ alarmEnabled.setChecked(pref.getBoolean("enabled", true));
+ updateGlobalUI(isEnabled);
+ }
+
+ boolean soundMaster = pref.getBoolean("sound_master_enabled", true);
+ m_checkBoxSoundEnabled.setChecked(soundMaster);
+ updateSoundUI(soundMaster);
+
+ SeekBar sb = (SeekBar) findViewById(R.id.seekBarVolume);
+ if (sb != null) sb.setProgress(pref.getInt("volume", 50));
+
+ // --- 3. Vibreur et Méthode d'arrêt ---
+ ToggleButton vibrateButton = (ToggleButton) findViewById(R.id.toggleButtonVibrate);
+ if (vibrateButton != null) vibrateButton.setChecked(pref.getBoolean("vibrate", false));
+
+ ToggleButton dismissButton = (ToggleButton) findViewById(R.id.toggleButtonUseDismiss);
+ if (dismissButton != null) dismissButton.setChecked(pref.getBoolean("dismiss", false));
+
+ // --- 4. Jours de la semaine ---
+ int[] checkBoxes = {R.id.checkBoxMondays, R.id.checkBoxTuesdays, R.id.checkBoxWednesdays,
+ R.id.checkBoxThursdays, R.id.checkBoxFridays, R.id.checkBoxSaturdays, R.id.checkBoxSundays};
+ String[] keys = {"mondays", "tuesdays", "wednesdays", "thursdays", "fridays", "saturdays", "sundays"};
+
+ for (int i = 0; i < checkBoxes.length; i++) {
+ CheckBox cb = (CheckBox) findViewById(checkBoxes[i]);
+ if (cb != null) cb.setChecked(pref.getBoolean(keys[i], true));
+ }
+
+ // --- 5. Son ---
+ String soundStr = pref.getString("sound", null);
+ if (soundStr != null && !soundStr.isEmpty()) {
+ m_soundUri = Uri.parse(soundStr);
+ } else {
+ m_soundUri = Settings.System.DEFAULT_ALARM_ALERT_URI;
+ }
+ // Validation du son (s'assure que le fichier existe toujours)
+ m_soundUri = checkSound(this, m_soundUri);
+ updateSoundButtonText();
+ // Mise à jour des textes (Heures/Durées)
+ updateLabels();
+ }
+
+ @Override
+ public void onClick(View v) {
+ int id = v.getId();
+
+ if (id == R.id.buttonSave) {
+ savePreferences();
+ } else if (id == R.id.buttonDiscard) {
+ finish();
+ } else if (id == R.id.buttonSound) {
+ pickSound();
+ } else if (id == R.id.txtLightStart) {
+ showTimePicker(lightStartH, lightStartM, (view, h, m) -> {
+ lightStartH = h; lightStartM = m;
+ ((TextView)v).setText(String.format("%02d:%02d", h, m));
+ updateLabels();
+ });
+ } /* else if (id == R.id.txtLightPreDelay) {
+ showDurationPicker(lightPreDelayMinutes, (val) -> {
+ lightPreDelayMinutes = val;
+ ((TextView)v).setText(val + " min");
+ updateLabels();
+ });
+ } else if (id == R.id.txtLightPostDelay) {
+ showDurationPicker(lightPostDelayMinutes, (val) -> {
+ lightPostDelayMinutes = val;
+ ((TextView)v).setText(val + " min");
+ updateLabels();
+ });
+ } */
+ else if (id == R.id.txtSoundStart) {
+ showTimePicker(soundStartH, soundStartM, (view, h, m) -> {
+ soundStartH = h; soundStartM = m;
+ updateLabels();
+ });
+ }
+ }
+
+ private void showTimePicker(int h, int m, OnTimeSetListener listener) {
+ new TimePickerDialog(this, listener, h, m, true).show();
+ }
+
+ private void showDurationPicker(int currentVal, OnDurationSetListener listener) {
+ final NumberPicker np = new NumberPicker(this);
+ np.setMinValue(1);
+ np.setMaxValue(120);
+ np.setValue(currentVal);
+ new AlertDialog.Builder(this)
+ .setTitle("Durée (minutes)")
+ .setView(np)
+ .setPositiveButton("OK", (d, w) -> listener.onSet(np.getValue()))
+ .show();
+ }
+
+ private void savePreferences() {
+ SharedPreferences pref = getSharedPreferences("main", MODE_PRIVATE);
+ SharedPreferences.Editor editor = pref.edit();
+ // 1. Calcul de l'heure de début absolue (le plus tôt entre lumière et son)
+ int lightStartTotal = lightStartH * 60 + lightStartM;
+ int soundStartTotal = soundStartH * 60 + soundStartM;
+ int globalStartTotal = Math.min(lightStartTotal, soundStartTotal);
+
+ // 2. Sauvegarde de l'heure de réveil système
+ editor.putInt("dawn_start_hour", globalStartTotal / 60);
+ editor.putInt("dawn_start_minute", globalStartTotal % 60);
+
+ // 3. Sauvegarde des décalages (en minutes par rapport au début global)
+ editor.putInt("light_pre_delay", lightPreDelayMinutes);
+ editor.putInt("light_post_delay", lightPostDelayMinutes);
+
+ editor.putBoolean("sound_master_enabled", m_checkBoxSoundEnabled.isChecked());
+
+ // 4. Jours de la semaine
+ int[] checkBoxes = {R.id.checkBoxMondays, R.id.checkBoxTuesdays, R.id.checkBoxWednesdays,
+ R.id.checkBoxThursdays, R.id.checkBoxFridays, R.id.checkBoxSaturdays, R.id.checkBoxSundays};
+ String[] keys = {"mondays", "tuesdays", "wednesdays", "thursdays", "fridays", "saturdays", "sundays"};
+
+ for (int i = 0; i < checkBoxes.length; i++) {
+ CheckBox cb = (CheckBox) findViewById(checkBoxes[i]);
+ if (cb != null) editor.putBoolean(keys[i], cb.isChecked());
+ }
+
+ // Volume
+ SeekBar sb = (SeekBar) findViewById(R.id.seekBarVolume);
+ if (sb != null) editor.putInt("volume", sb.getProgress());
+
+ // Vibreur et Dismiss
+ ToggleButton vibrateButton = (ToggleButton) findViewById(R.id.toggleButtonVibrate);
+ if (vibrateButton != null) editor.putBoolean("vibrate", vibrateButton.isChecked());
+
+ ToggleButton dismissButton = (ToggleButton) findViewById(R.id.toggleButtonUseDismiss);
+ if (dismissButton != null) editor.putBoolean("dismiss", dismissButton.isChecked());
+
+ // 4. Autres réglages (Volume, État alarme, etc.)
+ ToggleButton alarmEnabled = findViewById(R.id.toggleButtonAlarmEnabled);
+ if (alarmEnabled != null) {
+ editor.putBoolean("enabled", alarmEnabled.isChecked());
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
+ android.app.AlarmManager alarmManager = (android.app.AlarmManager) getSystemService(Context.ALARM_SERVICE);
+ if (alarmManager != null && !alarmManager.canScheduleExactAlarms()) {
+ // Si on n'a pas le droit, on envoie l'utilisateur dans les réglages
+ Intent intent = new Intent(android.provider.Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM,
+ Uri.parse("package:" + getPackageName()));
+ startActivity(intent);
+ // return; // On ne sauvegarde pas tant qu'on n'a pas la permission
+ }
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (!Settings.canDrawOverlays(this)) {
+ Toast.makeText(this, getString(R.string.overlay_permission), Toast.LENGTH_LONG).show();
+ Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
+ Uri.parse("package:" + getPackageName()));
+ startActivity(intent);
+ }
+ }
+ }
+
+ Log.d("FakeDawn", "Alarme activée : " + alarmEnabled.isChecked());
+ if (m_soundUri != null) {
+ editor.putString("sound", m_soundUri.toString());
+ } else {
+ editor.putString("sound", "");
+ }
+ editor.commit();
+ // editor.apply();
+
+ // 5. Relancer le service pour mettre à jour l'alarme système
+ Intent updateAlarm = new Intent(this, Alarm.class);
+ // updateAlarm.putExtra("force_update", true); // On peut ajouter un flag pour forcer
+ updateAlarm.putExtra(Alarm.EXTRA_SHOW_TOAST, true);
+
+ // 2. Sur Android 12+, il est parfois plus fiable de stopper
+ // le service avant de le relancer pour forcer un rafraîchissement complet
+ stopService(updateAlarm);
+ try {
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
+ startForegroundService(updateAlarm);
+ } else {
+ startService(updateAlarm);
+ }
+ } catch (Exception e) {
+ Log.e("FakeDawn", "Erreur lors du lancement du service Alarm" + e.getMessage());
+ }
+ Log.d("FakeDawn", "Preferences sauvegardées" + globalStartTotal / 60 + "h" + globalStartTotal % 60);
+ setResult(RESULT_OK);
+ finish();
+ }
+
+ private void pickSound() {
+ Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
+ // 1. Permet d'afficher l'option "Silencieux" ou "Aucun" en haut de la liste
+ intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
+
+ // 2. Titre de la fenêtre
+ intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, "Choisir une sonnerie");
+
+ // 3. Pré-sélectionner la sonnerie actuelle (si elle existe)
+ if (m_soundUri != null) {
+ intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, m_soundUri);
+ }
+ startActivityForResult(intent, REQUEST_PICK_SOUND);
+ }
+
+ private void changeSound(Uri sound) {
+ m_soundUri = sound;
+ }
+
+ public static Uri checkSound(Context context, Uri sound) {
+ //TODO: move in other class?
+ Uri[] sounds = {
+ sound,
+ Settings.System.DEFAULT_ALARM_ALERT_URI,
+ Settings.System.DEFAULT_RINGTONE_URI,
+ Settings.System.DEFAULT_NOTIFICATION_URI,
+ };
+ for (Uri s: sounds)
+ {
+ try {
+ InputStream tmp = context.getContentResolver().openInputStream(s);
+ tmp.close();
+ //TODO: toast if not first.
+ return s;
+ } catch (FileNotFoundException e) {
+ continue;
+ } catch (IOException e) {
+ continue;
+ }
+ }
+ //TODO: error toast
+ return null;
+ }
+ // --- MÉTHODES D'INTERFACE (Indispensables pour la compilation) ---
+
+ @Override public void onProgressChanged(SeekBar sb, int p, boolean b) {}
+ @Override public void onStartTrackingTouch(SeekBar sb) {}
+ @Override public void onStopTrackingTouch(SeekBar sb) {}
+ @Override public void colorChanged(int c) {}
+ @Override public void onTimeSet(TimePicker v, int h, int m) {}
+ @Override public void onTimesChanged(TimeSlider s) {} //}, int l, int r) {}
+
+ // --- CLASSES INTERNES ---
+
+ private class HelpListener implements DialogInterface.OnClickListener {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ // Logique du bouton Aide
+ }
+ }
+
+ interface OnDurationSetListener {
+ void onSet(int value);
+ }
+
+ private class VolumePreview {
+ // Logique de preview sonore
+ }
+}
\ No newline at end of file
diff --git a/src/org/balau/fakedawn/StartAtBoot.java b/app/src/main/java/org/balau/fakedawn/StartAtBoot.java
similarity index 100%
rename from src/org/balau/fakedawn/StartAtBoot.java
rename to app/src/main/java/org/balau/fakedawn/StartAtBoot.java
diff --git a/src/org/balau/fakedawn/TimeSlider.java b/app/src/main/java/org/balau/fakedawn/TimeSlider.java
similarity index 100%
rename from src/org/balau/fakedawn/TimeSlider.java
rename to app/src/main/java/org/balau/fakedawn/TimeSlider.java
diff --git a/res/drawable-hdpi/ic_launcher.png b/app/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from res/drawable-hdpi/ic_launcher.png
rename to app/src/main/res/drawable-hdpi/ic_launcher.png
diff --git a/res/drawable-ldpi/ic_launcher.png b/app/src/main/res/drawable-ldpi/ic_launcher.png
similarity index 100%
rename from res/drawable-ldpi/ic_launcher.png
rename to app/src/main/res/drawable-ldpi/ic_launcher.png
diff --git a/res/drawable-mdpi/ic_launcher.png b/app/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from res/drawable-mdpi/ic_launcher.png
rename to app/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/res/drawable-xhdpi/ic_launcher.png b/app/src/main/res/drawable-xhdpi/ic_launcher.png
similarity index 100%
rename from res/drawable-xhdpi/ic_launcher.png
rename to app/src/main/res/drawable-xhdpi/ic_launcher.png
diff --git a/res/drawable/sound.png b/app/src/main/res/drawable/sound.png
similarity index 100%
rename from res/drawable/sound.png
rename to app/src/main/res/drawable/sound.png
diff --git a/res/drawable/sunrise.png b/app/src/main/res/drawable/sunrise.png
similarity index 100%
rename from res/drawable/sunrise.png
rename to app/src/main/res/drawable/sunrise.png
diff --git a/res/layout/dawn.xml b/app/src/main/res/layout/dawn.xml
similarity index 100%
rename from res/layout/dawn.xml
rename to app/src/main/res/layout/dawn.xml
diff --git a/res/layout/license.xml b/app/src/main/res/layout/license.xml
similarity index 100%
rename from res/layout/license.xml
rename to app/src/main/res/layout/license.xml
diff --git a/app/src/main/res/layout/predawn.xml b/app/src/main/res/layout/predawn.xml
new file mode 100644
index 0000000..eeb4571
--- /dev/null
+++ b/app/src/main/res/layout/predawn.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/preferences.xml b/app/src/main/res/layout/preferences.xml
new file mode 100644
index 0000000..6c5b835
--- /dev/null
+++ b/app/src/main/res/layout/preferences.xml
@@ -0,0 +1,315 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/raw/license b/app/src/main/res/raw/license
similarity index 100%
rename from res/raw/license
rename to app/src/main/res/raw/license
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
new file mode 100644
index 0000000..7a4975f
--- /dev/null
+++ b/app/src/main/res/values-es/strings.xml
@@ -0,0 +1,46 @@
+
+
+
+ FakeDawn
+
+
+ Alarma activada
+ Alarma desactivada
+
+
+ Programación del amanecer
+ Repetir días
+ Sonido y Vibración
+ Método de parada
+
+
+ Pre-amanecer
+ Post-amanecer
+ %1$d min
+
+
+ Lun
+ Mar
+ Mié
+ Jue
+ Vie
+ Sáb
+ Dom
+
+
+ Activar sonido
+ Seleccionar tono
+ Volumen
+ Vibración activada
+ Vibración desactivada
+
+
+ Tocar pantalla para detener
+ Botón para detener
+
+
+ Reloj FakeDawn
+ La alarma está en curso…
+ Por favor, autorice el permiso de superposición de pantalla
+ Por favor, autorice el permiso de alarmas exactas
+
\ No newline at end of file
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
new file mode 100644
index 0000000..8504a58
--- /dev/null
+++ b/app/src/main/res/values-fr/strings.xml
@@ -0,0 +1,38 @@
+
+
+ Annuler
+ L\'alarme est Activée
+ L\'alarme est Désactivée
+ Programmation d\'Aube
+ Vibration On
+ Vibration Off
+ Veuillez accorder la permission de passage au premier plan
+ Veuillez accorder la permission d\'afficher des notifications
+ Veuillez accorder la permission de programmer une alarme
+ Veuillez accorder la permission d\\\'émettre un son
+ Veuillez accorder la permission d\\\'activer le vibreur
+ Lundis
+ Mardis
+ Mercredis
+ Jeudis
+ Vendredis
+ Samedis
+ Dimanches
+ Silence
+ Volume
+ Vibration
+ Méthode d\'arrêt
+ Toucher l\'écran
+ Presser le bouton Annuler
+ Sauver
+ Annuler
+ Désactivé
+ Activé avec signal sonore
+ Son choisi
+ Autorisez « Afficher sur d\'autres applis » pour que le réveil puisse s\'activer
+ Réveil FakeDawn
+ L\'alarme est en cours...
+ FakeDawn Activé
+ "Prochain réveil : "
+ Alarme FakeDawn
+
\ No newline at end of file
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
new file mode 100644
index 0000000..ae808ca
--- /dev/null
+++ b/app/src/main/res/values-it/strings.xml
@@ -0,0 +1,46 @@
+
+
+
+ FakeDawn
+
+
+ Allarme attivato
+ Allarme disattivato
+
+
+ Programmazione dell\'alba
+ Giorni di ripetizione
+ Suono e Vibrazione
+ Metodo di arresto
+
+
+ Pre-alba
+ Post-alba
+ %1$d min
+
+
+ Lun
+ Mar
+ Mer
+ Gio
+ Ven
+ Sab
+ Dom
+
+
+ Abilita suono
+ Seleziona suoneria
+ Volume
+ Vibrazione attiva
+ Vibrazione disattivata
+
+
+ Tocca lo schermo per fermare
+ Pulsante per fermare
+
+
+ Orologio FakeDawn
+ Sveglia in corso…
+ Per favore, autorizza il permesso di sovrapposizione
+ Per favore, autorizza il permesso per le sveglie precise
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..30d7b53
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,43 @@
+
+
+
+ FakeDawn
+ org.balau.fakedawn.Preferences
+ org.balau.fakedawn.Dawn
+ Dismiss
+ Alarm is Activated
+ Alarm is Disactivated
+ Sunrise Schedule
+ Vibration On
+ Vibration Off
+ Please allow Overlay permission
+ Please allow Notification permission
+ Please allow Alarm permission
+ Please allow Sound permission
+ Please allow Vibrate permission
+ Mondays
+ Tuesdays
+ Wednesdays
+ Thursdays
+ Fridays
+ Saturdays
+ Sundays
+ Silent
+ Volume
+ Vibration
+ Dismiss Method
+ Touch the screen
+ Press Dismiss Button
+ Save
+ Discard
+ Disactivated
+ Activated with sound
+ Sound Selected
+ Allow « Overlay Permission » to allow fakedawn to start
+ FakeDawn Clock
+ Alarme is running...
+ FakeDawn Activated
+ "Next alarm : "
+ FakeDawn Alarm
+ No week day selected!
+
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..3fe1b11
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..7bee3db
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,13 @@
+buildscript {
+ repositories {
+ google()
+ mavenCentral()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:8.2.0'
+ }
+}
+
+plugins {
+ id 'com.android.application' version '8.2.0' apply false
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..b70425d
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,3 @@
+properties
+android.useAndroidX=true
+android.enableJetifier=true
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..980502d
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..e7646de
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..faf9300
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,251 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..9b42019
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,94 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/res/layout/preferences.xml b/res/layout/preferences.xml
deleted file mode 100644
index a0ab3a9..0000000
--- a/res/layout/preferences.xml
+++ /dev/null
@@ -1,241 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
deleted file mode 100644
index 8d9452a..0000000
--- a/res/values/strings.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
- Hello World!
- Fake Dawn
- org.balau.fakedawn.Preferences
- org.balau.fakedawn.Dawn
- Dismiss
-
-
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..ee987ae
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,16 @@
+pluginManagement {
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+rootProject.name = "FakeDawn"
+include ':app'
\ No newline at end of file
diff --git a/src/org/balau/fakedawn/Dawn.java b/src/org/balau/fakedawn/Dawn.java
deleted file mode 100644
index 9394002..0000000
--- a/src/org/balau/fakedawn/Dawn.java
+++ /dev/null
@@ -1,268 +0,0 @@
-/**
- * Copyright 2012 Francesco Balducci
- *
- * This file is part of FakeDawn.
- *
- * FakeDawn is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * FakeDawn is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with FakeDawn. If not, see .
- */
-package org.balau.fakedawn;
-
-import java.util.Calendar;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.os.Bundle;
-import android.os.Handler;
-import android.provider.Settings;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.Window;
-import android.view.WindowManager;
-import android.widget.Button;
-
-public class Dawn extends Activity implements OnClickListener {
-
- private static int BRIGHTNESS_UPDATE_MILLIS = 10*1000;
- private static final String ALARM_START_MILLIS = "ALARM_START_MILLIS";
- private static int COLOR_OPAQUE = 0xFF000000;
-
- private long m_alarmStartMillis;
- private long m_alarmEndMillis;
- private Handler m_brightnessUpdaterHandler = new Handler();
- private Runnable m_brightnessUpdater = new Runnable() {
-
- @Override
- public void run() {
- updateBrightness(System.currentTimeMillis());
- m_brightnessUpdaterHandler.postDelayed(m_brightnessUpdater, BRIGHTNESS_UPDATE_MILLIS);
- }
- };
-
- private int m_dawnColor;
- private boolean m_ending;
-
- private boolean m_dismissbutton;
-
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- requestWindowFeature(Window.FEATURE_NO_TITLE);
- setContentView(R.layout.dawn);
- Window mainWindow = getWindow();
- mainWindow.addFlags(
- WindowManager.LayoutParams.FLAG_FULLSCREEN|
- WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON|
- WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED|
- WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD|
- WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
- mainWindow.clearFlags(
- WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON);
- WindowManager.LayoutParams mainWindowParams = mainWindow.getAttributes();
- mainWindowParams.screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_FULL;
- mainWindowParams.buttonBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_OFF;
- mainWindow.setAttributes(mainWindowParams);
-
-
- SharedPreferences pref = getApplicationContext().getSharedPreferences("main", MODE_PRIVATE);
- m_dismissbutton = pref.getBoolean("dismiss", false);
-
- Button dismissbutton= (Button)findViewById(R.id.dismiss_button);
- if (m_dismissbutton) {
- dismissbutton.setOnClickListener(this);
- dismissbutton.setVisibility(View.VISIBLE);
- } else {
- findViewById(R.id.dawn_background).setOnClickListener(this);
- dismissbutton.setVisibility(View.GONE);
- }
-
- Calendar alarmStart = AlarmReceiver.getAlarmStart(pref);
-
- long dawnStartMillis = alarmStart.getTimeInMillis();
- m_alarmStartMillis =
- dawnStartMillis + (pref.getInt("light_start", 0)*1000L*60L);
- if(savedInstanceState != null)
- {
- if(savedInstanceState.containsKey(ALARM_START_MILLIS))
- {
- m_alarmStartMillis = savedInstanceState.getLong(ALARM_START_MILLIS);
- }
- }
- m_alarmEndMillis = dawnStartMillis + (1000L*60L*pref.getInt("light_max", 15));
-
- //TODO: consistent default preferences.
- m_dawnColor = pref.getInt("color", 0x4040FF);
- Intent sound = new Intent(getApplicationContext(), DawnSound.class);
- sound.putExtra(DawnSound.EXTRA_INTENT_TYPE, DawnSound.EXTRA_INTENT_TYPE_START);
- sound.putExtra(DawnSound.EXTRA_VIBRATE, pref.getBoolean("vibrate", false));
- long soundStart = dawnStartMillis + (pref.getInt("sound_start", 15)*1000L*60L);
- long soundEnd = dawnStartMillis + (pref.getInt("sound_max", 15)*1000L*60L);
- sound.putExtra(DawnSound.EXTRA_SOUND_START_MILLIS, soundStart);
- sound.putExtra(DawnSound.EXTRA_SOUND_END_MILLIS, soundEnd);
- sound.putExtra(DawnSound.EXTRA_SOUND_URI,
- pref.getString("sound", Settings.System.DEFAULT_ALARM_ALERT_URI.toString()));
- if(pref.contains("volume"))
- {
- sound.putExtra(DawnSound.EXTRA_SOUND_VOLUME, pref.getInt("volume", 0));
- }
- startService(sound);
-
- updateBrightness(System.currentTimeMillis());
-
- }
-
- private void endDawn()
- {
- Context appContext = getApplicationContext();
- Intent sound = new Intent(appContext, DawnSound.class);
- stopService(sound);
- Intent stopAlarm = new Intent(appContext, AlarmReceiver.class);
- stopAlarm.setAction(AlarmReceiver.ACTION_STOP_ALARM);
- appContext.sendBroadcast(stopAlarm);
- m_ending = true;
- }
-
- /* (non-Javadoc)
- * @see android.app.Activity#onKeyDown(int, android.view.KeyEvent)
- */
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (!m_dismissbutton) {
- endDawn();
- this.finish();
- }
- return super.onKeyDown(keyCode, event);
- }
-
- public void onClick(View v) {
- endDawn();
- this.finish();
- }
-
- private int getColor(int rgb, int percent)
- {
- int r, g, b;
- int rgb_new;
-
- r = (rgb >> 16)&0xFF;
- g = (rgb >> 8)&0xFF;
- b = (rgb >> 0)&0xFF;
-
- if(percent > 100) percent = 100;
- if(percent < 0) percent = 0;
-
- r = (r*percent)/100;
- g = (g*percent)/100;
- b = (b*percent)/100;
-
- rgb_new = (r<<16) | (g<<8) | (b<<0);
-
- return rgb_new;
- }
-
- private void updateBrightness(long currentTimeMillis)
- {
- long level_percent;
- long millis_from_start;
- long dawnDurationMillis;
- int rgb;
-
- millis_from_start = currentTimeMillis - m_alarmStartMillis;
- dawnDurationMillis = m_alarmEndMillis - m_alarmStartMillis;
- if(dawnDurationMillis > 0)
- {
- level_percent = (100 * millis_from_start) / dawnDurationMillis;
- if(level_percent < 0) level_percent = 0;
- if(level_percent > 100) level_percent = 100;
- }
- else
- {
- level_percent = (millis_from_start >= 0)?100:0;
- }
- rgb = COLOR_OPAQUE | getColor(m_dawnColor, (int)level_percent);
- findViewById(R.id.dawn_background).setBackgroundColor(rgb);
- }
-
- /* (non-Javadoc)
- * @see android.app.Activity#onStart()
- */
- @Override
- protected void onStart() {
- super.onStart();
- m_brightnessUpdater.run();
- }
-
- /* (non-Javadoc)
- * @see android.app.Activity#onStop()
- */
- @Override
- protected void onStop() {
- super.onStop();
- m_brightnessUpdaterHandler.removeCallbacks(m_brightnessUpdater);
- }
-
- /* (non-Javadoc)
- * @see android.app.Activity#onPause()
- */
- @Override
- protected void onPause() {
- super.onPause();
- if (!m_ending)
- {
- Intent sound = new Intent(getApplicationContext(), DawnSound.class);
- sound.putExtra(DawnSound.EXTRA_INTENT_TYPE, DawnSound.EXTRA_INTENT_TYPE_INACTIVE);
- startService(sound);
- }
- }
-
- /* (non-Javadoc)
- * @see android.app.Activity#onResume()
- */
- @Override
- protected void onResume() {
- super.onResume();
- if (!m_ending)
- {
- Intent sound = new Intent(getApplicationContext(), DawnSound.class);
- sound.putExtra(DawnSound.EXTRA_INTENT_TYPE, DawnSound.EXTRA_INTENT_TYPE_ACTIVE);
- startService(sound);
- }
- }
-
- /* (non-Javadoc)
- * @see android.app.Activity#onSaveInstanceState(android.os.Bundle)
- */
- @Override
- protected void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putLong(ALARM_START_MILLIS, m_alarmStartMillis);
- }
-
- /* (non-Javadoc)
- * @see android.app.Activity#onDestroy()
- */
- @Override
- protected void onDestroy() {
- super.onDestroy();
- if (!m_ending)
- {
- endDawn();
- }
- }
-
-}
diff --git a/src/org/balau/fakedawn/Preferences.java b/src/org/balau/fakedawn/Preferences.java
deleted file mode 100644
index 18038fa..0000000
--- a/src/org/balau/fakedawn/Preferences.java
+++ /dev/null
@@ -1,764 +0,0 @@
-/**
- * Copyright 2012 Francesco Balducci
- *
- * This file is part of FakeDawn.
- *
- * FakeDawn is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * FakeDawn is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with FakeDawn. If not, see .
- */
-
-package org.balau.fakedawn;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-
-import org.balau.fakedawn.ColorPickerDialog.OnColorChangedListener;
-import org.balau.fakedawn.TimeSlider.DawnTime;
-import org.balau.fakedawn.TimeSlider.OnTimesChangedListener;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.TimePickerDialog;
-import android.app.TimePickerDialog.OnTimeSetListener;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.media.AudioManager;
-import android.media.MediaPlayer;
-import android.media.MediaPlayer.OnCompletionListener;
-import android.media.MediaPlayer.OnErrorListener;
-import android.media.Ringtone;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.provider.Settings;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.Button;
-import android.widget.CheckBox;
-import android.widget.SeekBar;
-import android.widget.SeekBar.OnSeekBarChangeListener;
-import android.widget.TimePicker;
-import android.widget.ToggleButton;
-
-/**
- * @author francesco
- *
- */
-public class Preferences extends Activity implements OnClickListener, OnSeekBarChangeListener, OnColorChangedListener, OnTimeSetListener, OnTimesChangedListener {
-
- private static final int REQUEST_PICK_SOUND = 0;
- private static final int COLOR_OPAQUE = 0xFF000000;
- private static final int COLOR_RGB_MASK = 0x00FFFFFF;
-
- private static final int TIME_DAWN_START = 0;
- private static final int TIME_DAWN_END = 1;
- private static final int TIME_SOUND_START = 2;
- private static final int TIME_SOUND_END = 3;
-
- private Uri m_soundUri;
- private VolumePreview m_preview = new VolumePreview();
- private int m_dawnColor;
- private int m_clickedTime;
-
- private Handler m_sliderResizerHandler = new Handler();
- private Runnable m_sliderResizer = new Runnable() {
-
- @Override
- public void run() {
- resizeSliders();
- }
- };
- private static final int RESIZE_SLIDERS_DELAY_MILLIS = 1000;
- private static final int SLIDERS_PADDING_MINUTES = 10;
-
- private boolean m_showHelp = false;
- private HelpListener m_helpListener = new HelpListener();
-
- /* (non-Javadoc)
- * @see android.app.Activity#onCreate(android.os.Bundle)
- */
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- setContentView(R.layout.preferences);
-
- Button saveButton = (Button) findViewById(R.id.buttonSave);
- saveButton.setOnClickListener(this);
- Button discardButton = (Button) findViewById(R.id.buttonDiscard);
- discardButton.setOnClickListener(this);
- Button soundButton = (Button) findViewById(R.id.buttonSound);
- soundButton.setOnClickListener(this);
-
- SeekBar seekBarVolume = (SeekBar)findViewById(R.id.seekBarVolume);
- seekBarVolume.setOnSeekBarChangeListener(this);
-
- SharedPreferences pref = getApplicationContext().getSharedPreferences("main", MODE_PRIVATE);
-
- CheckBox cb;
-
- ToggleButton alarmEnabledButton = (ToggleButton) findViewById(R.id.toggleButtonAlarmEnabled);
- alarmEnabledButton.setChecked(pref.getBoolean("enabled", false));
-
- TimeSlider lightSlider = (TimeSlider)findViewById(R.id.timeSlider1);
- TimeSlider soundSlider = (TimeSlider)findViewById(R.id.timeSlider2);
-
- lightSlider.setOnClickListener(this);
- lightSlider.setOnTimesChangedListener(this);
- soundSlider.setOnClickListener(this);
- soundSlider.setOnTimesChangedListener(this);
-
- //TODO: defaults as fields
- DawnTime dawnStart = new DawnTime(
- pref.getInt("dawn_start_hour", 8),
- pref.getInt("dawn_start_minute", 0));
- lightSlider.setLeftTime(dawnStart.getMinutes() + pref.getInt("light_start", 0));
- lightSlider.setRightTime(dawnStart.getMinutes() + pref.getInt("light_max", 15));
- soundSlider.setLeftTime(dawnStart.getMinutes() + pref.getInt("sound_start", 15));
- soundSlider.setRightTime(dawnStart.getMinutes() + pref.getInt("sound_max", 30));
-
- cb = (CheckBox) findViewById(R.id.checkBoxMondays);
- cb.setChecked(pref.getBoolean("mondays", true));
- cb = (CheckBox) findViewById(R.id.checkBoxTuesdays);
- cb.setChecked(pref.getBoolean("tuesdays", true));
- cb = (CheckBox) findViewById(R.id.checkBoxWednesdays);
- cb.setChecked(pref.getBoolean("wednesdays", true));
- cb = (CheckBox) findViewById(R.id.checkBoxThursdays);
- cb.setChecked(pref.getBoolean("thursdays", true));
- cb = (CheckBox) findViewById(R.id.checkBoxFridays);
- cb.setChecked(pref.getBoolean("fridays", true));
- cb = (CheckBox) findViewById(R.id.checkBoxSaturdays);
- cb.setChecked(pref.getBoolean("saturdays", true));
- cb = (CheckBox) findViewById(R.id.checkBoxSundays);
- cb.setChecked(pref.getBoolean("sundays", true));
-
- updateColor(pref.getInt("color", 0x4040FF));
-
- AudioManager am = (AudioManager)getSystemService(AUDIO_SERVICE);
- int maxVolume = am.getStreamMaxVolume(AudioManager.STREAM_ALARM);
- seekBarVolume.setMax(maxVolume);
- int volume = pref.getInt("volume", maxVolume);
- if(volume < 0) volume = 0;
- if(volume > maxVolume) volume = maxVolume;
- seekBarVolume.setProgress(volume);
-
- ToggleButton vibrateButton = (ToggleButton) findViewById(R.id.toggleButtonVibrate);
- vibrateButton.setChecked(pref.getBoolean("vibrate", false));
-
- ToggleButton dissmissButton = (ToggleButton) findViewById(R.id.toggleButtonUseDismiss);
- dissmissButton.setChecked(pref.getBoolean("dismiss", false));
-
- Uri sound = Uri.parse(
- pref.getString("sound", Settings.System.DEFAULT_ALARM_ALERT_URI.toString()));
- changeSound(sound);
-
- resizeSliders();
-
- String firstTimeVersion = pref.getString("first_time_version", "");
- String version = "";
- try {
- version = getPackageManager().getPackageInfo(getPackageName(), 0).versionName;
- } catch (NameNotFoundException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- m_showHelp = firstTimeVersion == "" || !firstTimeVersion.equals(version);
-
- Log.d("FakeDawn", "Preferences loaded.");
- }
-
- /* (non-Javadoc)
- * @see android.app.Activity#onStart()
- */
- @Override
- protected void onStart() {
- super.onStart();
- if(m_showHelp)
- {
- showHelp();
- }
- }
-
- private void showHelp()
- {
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setPositiveButton("Close", (DialogInterface.OnClickListener) m_helpListener);
- builder.setNeutralButton("Read License", (DialogInterface.OnClickListener) m_helpListener);
- String message = "";
- message = message.concat("");
- message += "Fake Dawn gradually increases brightness and sound volume to lead you out of deep sleep and wake you up gently.\n\n";
- message += "Choose when the brightness will start to rise and when it reaches the max using the first horizontal bar.\n\n";
- message += "Adjust when and how the sound will play using the second bar.";
- builder.setMessage(message);
- builder.create().show();
- }
-
- private class HelpListener implements DialogInterface.OnClickListener {
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
-
- SharedPreferences pref = getApplicationContext().getSharedPreferences("main", MODE_PRIVATE);
- String version = "";
- try {
- version = getPackageManager().getPackageInfo(getPackageName(), 0).versionName;
- } catch (NameNotFoundException e) {
- e.printStackTrace();
- }
- SharedPreferences.Editor editor = pref.edit();
- editor.putString("first_time_version", version);
- editor.commit();
-
- switch(which)
- {
- case DialogInterface.BUTTON_POSITIVE:
- break;
- case DialogInterface.BUTTON_NEUTRAL:
- startActivity(new Intent(getApplicationContext(), License.class));
- break;
- case DialogInterface.BUTTON_NEGATIVE:
- break;
- }
- }
-
- }
-
- private void resizeSliders()
- {
- TimeSlider lightSlider = (TimeSlider)findViewById(R.id.timeSlider1);
- TimeSlider soundSlider = (TimeSlider)findViewById(R.id.timeSlider2);
-
- DawnTime light_start = lightSlider.getLeftTime();
- DawnTime light_end = lightSlider.getRightTime();
- DawnTime sound_start;
- DawnTime sound_end;
-
- if(soundSlider.isEnabled())
- {
- sound_start = soundSlider.getLeftTime();
- sound_end = soundSlider.getRightTime();
- }
- else
- {
- //If disabled, it follows the end of the light slider.
- sound_start = lightSlider.getRightTime();
- sound_end = new DawnTime(sound_start.getMinutes());
- soundSlider.setLeftTime(sound_start.getHour(), sound_start.getMinute());
- soundSlider.setRightTime(sound_end.getHour(), sound_end.getMinute());
- }
-
- int minTime = Math.max(
- Math.min(
- light_start.getMinutes(),
- sound_start.getMinutes()) - SLIDERS_PADDING_MINUTES,
- 0);
- int maxTime = Math.max(
- light_end.getMinutes(),
- sound_end.getMinutes()) + SLIDERS_PADDING_MINUTES;
-
- int minutes_in_day = 60*24;
- if(minTime + SLIDERS_PADDING_MINUTES >= minutes_in_day)
- {
- // shift everything to the day
- int days = (minTime+SLIDERS_PADDING_MINUTES)/minutes_in_day; //floor
- int minutes_to_subtract = days*minutes_in_day;
-
- minTime = Math.max(minTime - minutes_to_subtract, 0);
- maxTime -= minutes_to_subtract;
- light_start = new DawnTime(light_start.getMinutes() - minutes_to_subtract);
- light_end = new DawnTime(light_end.getMinutes() - minutes_to_subtract);
- sound_start = new DawnTime(sound_start.getMinutes() - minutes_to_subtract);
- sound_end = new DawnTime(sound_end.getMinutes() - minutes_to_subtract);
-
- lightSlider.setLeftTime(light_start.getHour(), light_start.getMinute());
- lightSlider.setRightTime(light_end.getHour(), light_end.getMinute());
- soundSlider.setLeftTime(sound_start.getHour(), sound_start.getMinute());
- soundSlider.setRightTime(sound_end.getHour(), sound_end.getMinute());
-
- }
- DawnTime start = new DawnTime(minTime);
-
- lightSlider.setStartTime(start.getHour(), start.getMinute());
- lightSlider.setSpanTime(maxTime - minTime);
-
- soundSlider.setStartTime(start.getHour(), start.getMinute());
- soundSlider.setSpanTime(maxTime - minTime);
-
- }
-
- private void updateColor(int color)
- {
- m_dawnColor = color & COLOR_RGB_MASK;
- TimeSlider ts = (TimeSlider)findViewById(R.id.timeSlider1);
- ts.setRectColor(m_dawnColor|COLOR_OPAQUE);
- }
-
- public void onClick(View v) {
- ColorPickerDialog colorDialog;
- TimeSlider ts;
- TimePickerDialog tpd;
-
- switch(v.getId())
- {
- case R.id.buttonSave:
- SharedPreferences pref = getApplicationContext().getSharedPreferences("main", MODE_PRIVATE);
- SharedPreferences.Editor editor = pref.edit();
- TimeSlider lightSlider = (TimeSlider)findViewById(R.id.timeSlider1);
- TimeSlider soundSlider = (TimeSlider)findViewById(R.id.timeSlider2);
- int dawn_start_minutes;
- if(lightSlider.getLeftTime().getMinutes() < soundSlider.getLeftTime().getMinutes())
- {
- editor.putInt("dawn_start_hour", lightSlider.getLeftTime().getHourOfDay());
- editor.putInt("dawn_start_minute", lightSlider.getLeftTime().getMinute());
- dawn_start_minutes = lightSlider.getLeftTime().getMinutes();
- }
- else
- {
- editor.putInt("dawn_start_hour", soundSlider.getLeftTime().getHourOfDay());
- editor.putInt("dawn_start_minute", soundSlider.getLeftTime().getMinute());
- dawn_start_minutes = soundSlider.getLeftTime().getMinutes();
- }
-
- editor.putInt("color", m_dawnColor);
-
-
- CheckBox cb;
-
- ToggleButton alarmEnabledButton = (ToggleButton) findViewById(R.id.toggleButtonAlarmEnabled);
- editor.putBoolean("enabled", alarmEnabledButton.isChecked());
-
- cb = (CheckBox) findViewById(R.id.checkBoxMondays);
- editor.putBoolean("mondays", cb.isChecked());
- cb = (CheckBox) findViewById(R.id.checkBoxTuesdays);
- editor.putBoolean("tuesdays", cb.isChecked());
- cb = (CheckBox) findViewById(R.id.checkBoxWednesdays);
- editor.putBoolean("wednesdays", cb.isChecked());
- cb = (CheckBox) findViewById(R.id.checkBoxThursdays);
- editor.putBoolean("thursdays", cb.isChecked());
- cb = (CheckBox) findViewById(R.id.checkBoxFridays);
- editor.putBoolean("fridays", cb.isChecked());
- cb = (CheckBox) findViewById(R.id.checkBoxSaturdays);
- editor.putBoolean("saturdays", cb.isChecked());
- cb = (CheckBox) findViewById(R.id.checkBoxSundays);
- editor.putBoolean("sundays", cb.isChecked());
-
- editor.putInt("light_start",lightSlider.getLeftTime().getMinutes() - dawn_start_minutes);
- editor.putInt("light_max",lightSlider.getRightTime().getMinutes() - dawn_start_minutes);
-
- if(m_soundUri == null)
- {
- editor.putString("sound", "");
- }
- else
- {
- editor.putString("sound", m_soundUri.toString());
- }
-
- editor.putInt("sound_start",soundSlider.getLeftTime().getMinutes() - dawn_start_minutes);
- editor.putInt("sound_max",soundSlider.getRightTime().getMinutes() - dawn_start_minutes);
-
- SeekBar sb = (SeekBar)findViewById(R.id.seekBarVolume);
- editor.putInt("volume", sb.getProgress());
-
- ToggleButton vibrateButton = (ToggleButton) findViewById(R.id.toggleButtonVibrate);
- editor.putBoolean("vibrate", vibrateButton.isChecked());
-
- ToggleButton dissmissButton = (ToggleButton) findViewById(R.id.toggleButtonUseDismiss);
- editor.putBoolean("dismiss", dissmissButton.isChecked());
-
- editor.commit();
-
- Intent updateAlarm = new Intent(getApplicationContext(), Alarm.class);
- updateAlarm.putExtra(Alarm.EXTRA_SHOW_TOAST, true);
- getApplicationContext().startService(updateAlarm);
- Log.d("FakeDawn", "Preferences saved.");
- finish();
- break;
- case R.id.buttonDiscard:
- finish();
- break;
- case R.id.buttonSound:
- Intent pickSound = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
- pickSound.putExtra(
- RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT,
- true);
- pickSound.putExtra(
- RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT,
- false);
- pickSound.putExtra(
- RingtoneManager.EXTRA_RINGTONE_TYPE,
- RingtoneManager.TYPE_ALL);
- pickSound.putExtra(
- RingtoneManager.EXTRA_RINGTONE_TITLE,
- "Pick Alarm Sound");
- if(m_soundUri != null)
- {
- pickSound.putExtra(
- RingtoneManager.EXTRA_RINGTONE_EXISTING_URI,
- m_soundUri);
- }
- startActivityForResult(pickSound, REQUEST_PICK_SOUND);
- break;
- case R.id.timeSlider1:
- ts = (TimeSlider)v;
- switch(ts.getLastTouched())
- {
- case TimeSlider.TOUCH_ALL:
- colorDialog = new ColorPickerDialog(this, this, m_dawnColor);
- colorDialog.show();
- break;
- case TimeSlider.TOUCH_LEFT:
- tpd = new TimePickerDialog(
- this,
- this,
- ts.getLeftTime().getHourOfDay(),
- ts.getLeftTime().getMinute(),
- true);
- m_clickedTime = TIME_DAWN_START;
- tpd.show();
- break;
- case TimeSlider.TOUCH_RIGHT:
- tpd = new TimePickerDialog(
- this,
- this,
- ts.getRightTime().getHourOfDay(),
- ts.getRightTime().getMinute(),
- true);
- m_clickedTime = TIME_DAWN_END;
- tpd.show();
- break;
- }
- break;
- case R.id.timeSlider2:
- ts = (TimeSlider)v;
- switch(ts.getLastTouched())
- {
- case TimeSlider.TOUCH_ALL:
- break;
- case TimeSlider.TOUCH_LEFT:
- tpd = new TimePickerDialog(
- this,
- this,
- ts.getLeftTime().getHourOfDay(),
- ts.getLeftTime().getMinute(),
- true);
- m_clickedTime = TIME_SOUND_START;
- tpd.show();
- break;
- case TimeSlider.TOUCH_RIGHT:
- tpd = new TimePickerDialog(
- this,
- this,
- ts.getRightTime().getHourOfDay(),
- ts.getRightTime().getMinute(),
- true);
- m_clickedTime = TIME_SOUND_END;
- tpd.show();
- break;
- }
- break;
- }
- }
-
- private void setSoundViewsEnabled(boolean enabled)
- {
- SeekBar seekBarVolume = (SeekBar)findViewById(R.id.seekBarVolume);
- ToggleButton vibrateButton = (ToggleButton) findViewById(R.id.toggleButtonVibrate);
- TimeSlider soundSlider = (TimeSlider)findViewById(R.id.timeSlider2);
-
- seekBarVolume.setEnabled(enabled);
- vibrateButton.setEnabled(enabled);
- soundSlider.setEnabled(enabled);
- }
-
- private void setSoundButtonText(String text)
- {
- Button soundButton = (Button) findViewById(R.id.buttonSound);
- soundButton.setText(text);
- }
-
- private void disableSound()
- {
- setSoundButtonText("Silent");
- setSoundViewsEnabled(false);
- m_preview.stop();
- }
-
- private void enableSound(Uri sound)
- {
- setSoundButtonText(
- RingtoneManager.getRingtone(this, sound).getTitle(this));
- setSoundViewsEnabled(true);
- m_preview.setSoundUri(this, sound);
- }
-
- private void changeSound(Uri sound)
- {
- Uri sanitizedSound;
- if (sound == null || sound.toString().isEmpty())
- {
- sanitizedSound = null;
- }
- else
- {
- sanitizedSound = checkSound(this, sound);
- }
-
- if (sanitizedSound == null)
- {
- m_soundUri = null;
- disableSound();
- }
- else
- {
- m_soundUri = sanitizedSound;
- enableSound(sanitizedSound);
- }
- }
-
- public static Uri checkSound(Context context, Uri sound) {
- //TODO: move in other class?
- Uri[] sounds = {
- sound,
- Settings.System.DEFAULT_ALARM_ALERT_URI,
- Settings.System.DEFAULT_RINGTONE_URI,
- Settings.System.DEFAULT_NOTIFICATION_URI,
- };
- for (Uri s: sounds)
- {
- try {
- InputStream tmp = context.getContentResolver().openInputStream(s);
- tmp.close();
- //TODO: toast if not first.
- return s;
- } catch (FileNotFoundException e) {
- continue;
- } catch (IOException e) {
- continue;
- }
- }
- //TODO: error toast
- return null;
- }
-
- /* (non-Javadoc)
- * @see android.app.Activity#onActivityResult(int, int, android.content.Intent)
- */
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- if(requestCode == REQUEST_PICK_SOUND)
- {
- if(resultCode == RESULT_OK)
- {
- Uri sound = (Uri) data.getParcelableExtra(
- RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
- changeSound(sound);
- }
- }
- }
-
- @Override
- public void onProgressChanged(SeekBar seekBar, int progress,
- boolean fromUser) {
- if(seekBar.getId() == R.id.seekBarVolume)
- {
- if(fromUser)
- {
- m_preview.previewVolume(progress);
- }
- }
- }
-
- @Override
- public void onStartTrackingTouch(SeekBar seekBar) { }
-
- @Override
- public void onStopTrackingTouch(SeekBar seekBar) { }
-
- /* (non-Javadoc)
- * @see android.app.Activity#onStop()
- */
- @Override
- protected void onStop() {
- super.onStop();
- m_preview.stop();
- m_sliderResizerHandler.removeCallbacks(m_sliderResizer);
- }
-
- private class VolumePreview implements OnCompletionListener, OnErrorListener {
-
- /**
- *
- */
- public VolumePreview() {
- m_player.setOnErrorListener(this);
- m_player.setOnCompletionListener(this);
- }
-
- private MediaPlayer m_player = new MediaPlayer();
- private boolean m_playerReady = false;
-
- public void setSoundUri(Context context, Uri soundUri) {
- m_player.reset();
- m_player.setAudioStreamType(AudioManager.STREAM_ALARM);
- if(soundUri != null)
- {
- try {
- m_player.setDataSource(context, soundUri);
- m_playerReady = true;
- } catch (IllegalArgumentException e) {
- e.printStackTrace();
- Log.e("FakeDawn", "setSoundUri", e);
- } catch (SecurityException e) {
- e.printStackTrace();
- Log.e("FakeDawn", "setSoundUri", e);
- } catch (IllegalStateException e) {
- e.printStackTrace();
- Log.e("FakeDawn", "setSoundUri", e);
- } catch (IOException e) {
- e.printStackTrace();
- Log.e("FakeDawn", "setSoundUri", e);
- }
- }
- }
-
- public void stop()
- {
- if(m_playerReady)
- {
- if(m_player.isPlaying())
- {
- m_player.stop();
- }
- }
- }
-
- public void previewVolume(int volume)
- {
- if(m_playerReady)
- {
- try {
- if(!m_player.isPlaying())
- {
- m_player.prepare();
- m_player.start();
- }
- AudioManager am = (AudioManager)getSystemService(AUDIO_SERVICE);
- int maxVolume = am.getStreamMaxVolume(AudioManager.STREAM_ALARM);
- if(volume < 0) volume = 0;
- if(volume > maxVolume) volume = maxVolume;
- am.setStreamVolume(AudioManager.STREAM_ALARM, volume, 0);
- } catch (IllegalStateException e) {
- e.printStackTrace();
- Log.e("FakeDawn", "previewVolume", e);
- m_playerReady = false;
- } catch (IOException e) {
- e.printStackTrace();
- Log.e("FakeDawn", "previewVolume", e);
- m_playerReady = false;
- }
- }
- }
-
- @Override
- public boolean onError(MediaPlayer mp, int what, int extra) {
- Log.e("FakeDawn", String.format("MediaPlayer error. what: %d, extra: %d", what, extra));
- mp.reset();
- m_playerReady = false;
- return true;
- }
-
- @Override
- public void onCompletion(MediaPlayer mp) {
- mp.stop();
- }
-
- }
-
- @Override
- public void colorChanged(int color) {
- updateColor(color);
- }
-
- @Override
- public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
- TimeSlider lightSlider = (TimeSlider)findViewById(R.id.timeSlider1);
- TimeSlider soundSlider = (TimeSlider)findViewById(R.id.timeSlider2);
- DawnTime dt = new DawnTime(hourOfDay, minute);
- int new_minutes = dt.getMinutes();
- int delta_minutes = 0;
-
- switch(m_clickedTime)
- {
- case TIME_DAWN_START:
- delta_minutes = new_minutes - lightSlider.getLeftTime().getMinutes();
- break;
- case TIME_DAWN_END:
- delta_minutes = new_minutes - lightSlider.getRightTime().getMinutes();
- break;
- case TIME_SOUND_START:
- delta_minutes = new_minutes - soundSlider.getLeftTime().getMinutes();
- break;
- case TIME_SOUND_END:
- delta_minutes = new_minutes - soundSlider.getRightTime().getMinutes();
- break;
- }
- // We need to distinguish the direction, because a marker can't be set to surpass the other.
- // Left marker must stay left, right marker must stay right.
- if (delta_minutes > 0)
- {
- // We shift everything to the right, so we move first the right markers, then the left ones.
- lightSlider.setRightTime(lightSlider.getRightTime().getMinutes() + delta_minutes);
- soundSlider.setRightTime(soundSlider.getRightTime().getMinutes() + delta_minutes);
- lightSlider.setLeftTime(lightSlider.getLeftTime().getMinutes() + delta_minutes);
- soundSlider.setLeftTime(soundSlider.getLeftTime().getMinutes() + delta_minutes);
- }
- else if (delta_minutes < 0)
- {
- // We shift everything to the left, so we move first the left markers, then the right ones.
- lightSlider.setLeftTime(lightSlider.getLeftTime().getMinutes() + delta_minutes);
- soundSlider.setLeftTime(soundSlider.getLeftTime().getMinutes() + delta_minutes);
- lightSlider.setRightTime(lightSlider.getRightTime().getMinutes() + delta_minutes);
- soundSlider.setRightTime(soundSlider.getRightTime().getMinutes() + delta_minutes);
- }
- resizeSliders();
- }
-
- @Override
- public void onTimesChanged(TimeSlider s) {
- m_sliderResizerHandler.removeCallbacks(m_sliderResizer);
- m_sliderResizerHandler.postDelayed(m_sliderResizer, RESIZE_SLIDERS_DELAY_MILLIS);
- }
-
- /* (non-Javadoc)
- * @see android.app.Activity#onKeyDown(int, android.view.KeyEvent)
- */
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- // TODO Auto-generated method stub
- if(keyCode == KeyEvent.KEYCODE_MENU)
- {
- showHelp();
- return true;
- }
- else
- return super.onKeyDown(keyCode, event);
- }
-}
diff --git a/tests/src/org/balau/fakedawn/test/PreferencesTest.java b/tests/src/org/balau/fakedawn/test/PreferencesTest.java
index 9d104c7..fe391e6 100644
--- a/tests/src/org/balau/fakedawn/test/PreferencesTest.java
+++ b/tests/src/org/balau/fakedawn/test/PreferencesTest.java
@@ -5,11 +5,9 @@
import org.balau.fakedawn.Preferences;
-import android.content.Intent;
import android.test.ActivityUnitTestCase;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
-import android.widget.Button;
/**
* @author francesco
diff --git a/tests/tests.iml b/tests/tests.iml
new file mode 100644
index 0000000..917922f
--- /dev/null
+++ b/tests/tests.iml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file