diff --git a/CHANGES.md b/CHANGES.md
index 4b78061..9878469 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -2,6 +2,15 @@
+## v2.3.0: Released at 2024-05-11
+
+- Added shortcut to vibrate the current time
+- Increased vibration intensity for Android versions >= 8
+- Fixed drifting alarms
+- Updated to ViewPager2 and other small ui fixes
+
+
+
## v2.2.0: Released at 2024-03-25
- Support for Android 14 (SDK 34)
diff --git a/app/build.gradle b/app/build.gradle
index ab32acb..0f2c6c6 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -31,8 +31,8 @@ android {
minSdkVersion 16
targetSdkVersion 34
multiDexEnabled true
- versionCode 10
- versionName '2.2.0'
+ versionCode 11
+ versionName '2.3.0'
// project website
buildConfigField 'String', 'CONTACT_EMAIL_ADDRESS', '"tactile-clock@eric-scheibler.de"'
buildConfigField 'String', 'PROJECT_WEBSITE', '"https://github.com/scheibler/TactileClock"'
@@ -72,7 +72,7 @@ dependencies {
implementation 'androidx.drawerlayout:drawerlayout:1.2.0'
implementation 'androidx.fragment:fragment:1.6.2'
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0'
- implementation 'androidx.viewpager:viewpager:1.0.0'
+ implementation 'androidx.viewpager2:viewpager2:1.0.0'
// material design
implementation 'com.google.android.material:material:1.11.0'
// guava
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 8e65320..eeeed07 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -23,13 +23,23 @@
+
+
+
+
+
= android.os.Build.VERSION_CODES.LOLLIPOP) {
+ finishAndRemoveTask();
+ } else {
+ finish();
+ }
+ }
+ }
+ };
+
+}
diff --git a/app/src/main/java/de/eric_scheibler/tactileclock/ui/fragment/AbstractFragment.java b/app/src/main/java/de/eric_scheibler/tactileclock/ui/fragment/AbstractFragment.java
deleted file mode 100644
index ae66b7f..0000000
--- a/app/src/main/java/de/eric_scheibler/tactileclock/ui/fragment/AbstractFragment.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package de.eric_scheibler.tactileclock.ui.fragment;
-
-import androidx.fragment.app.DialogFragment;
-
-
-public abstract class AbstractFragment extends DialogFragment {
-
- /**
- * to be implemented
- */
-
- public abstract void fragmentVisible();
- public abstract void fragmentInvisible();
-
-
- /**
- * pause and resume
- */
-
- private boolean isResumed = false;
- private boolean isVisible = false;
-
- @Override public void onPause() {
- super.onPause();
- if (getDialog() != null || isVisible) { // fragment is a dialog or embedded and visible
- fragmentInvisible();
- }
- isResumed = false;
- }
-
- @Override public void onResume() {
- super.onResume();
- if (getDialog() != null || isVisible) { // fragment is a dialog or embedded and visible
- fragmentVisible();
- }
- isResumed = true;
- }
-
- @Override public void setUserVisibleHint(boolean isVisibleToUser) {
- super.setUserVisibleHint(isVisibleToUser);
- if (isResumed) {
- if (isVisibleToUser) {
- fragmentVisible();
- } else {
- fragmentInvisible();
- }
- }
- isVisible = isVisibleToUser;
- }
-
-}
diff --git a/app/src/main/java/de/eric_scheibler/tactileclock/ui/fragment/PowerButtonFragment.java b/app/src/main/java/de/eric_scheibler/tactileclock/ui/fragment/PowerButtonFragment.java
index 30a4d98..8ce0f21 100644
--- a/app/src/main/java/de/eric_scheibler/tactileclock/ui/fragment/PowerButtonFragment.java
+++ b/app/src/main/java/de/eric_scheibler/tactileclock/ui/fragment/PowerButtonFragment.java
@@ -17,9 +17,10 @@
import de.eric_scheibler.tactileclock.ui.dialog.SelectIntegerDialog.Token;
import de.eric_scheibler.tactileclock.ui.dialog.SelectIntegerDialog;
import de.eric_scheibler.tactileclock.utils.SettingsManager;
+import androidx.fragment.app.Fragment;
-public class PowerButtonFragment extends AbstractFragment implements IntegerSelector {
+public class PowerButtonFragment extends Fragment implements IntegerSelector {
// Store instance variables
private SettingsManager settingsManagerInstance;
@@ -34,8 +35,8 @@ public static PowerButtonFragment newInstance() {
return powerButtonFragmentInstance;
}
- @Override public void onAttach(Context context) {
- super.onAttach(context);
+ @Override public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
settingsManagerInstance = new SettingsManager();
}
@@ -91,10 +92,12 @@ public void onClick(View view) {
});
}
- @Override public void fragmentInvisible() {
+ @Override public void onPause() {
+ super.onPause();
}
- @Override public void fragmentVisible() {
+ @Override public void onResume() {
+ super.onResume();
updateUI();
}
diff --git a/app/src/main/java/de/eric_scheibler/tactileclock/ui/fragment/WatchFragment.java b/app/src/main/java/de/eric_scheibler/tactileclock/ui/fragment/WatchFragment.java
index 77c62ca..6b0fc7e 100644
--- a/app/src/main/java/de/eric_scheibler/tactileclock/ui/fragment/WatchFragment.java
+++ b/app/src/main/java/de/eric_scheibler/tactileclock/ui/fragment/WatchFragment.java
@@ -1,6 +1,6 @@
package de.eric_scheibler.tactileclock.ui.fragment;
-
+import android.app.AlarmManager;
import android.content.Context;
import android.os.Bundle;
@@ -18,10 +18,17 @@
import de.eric_scheibler.tactileclock.ui.dialog.SelectIntegerDialog.Token;
import de.eric_scheibler.tactileclock.ui.dialog.SelectIntegerDialog;
import de.eric_scheibler.tactileclock.utils.SettingsManager;
+import androidx.fragment.app.Fragment;
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.content.Intent;
+import android.provider.Settings;
+import de.eric_scheibler.tactileclock.utils.ApplicationInstance;
+import timber.log.Timber;
-public class WatchFragment extends AbstractFragment implements IntegerSelector {
+public class WatchFragment extends Fragment implements IntegerSelector {
// Store instance variables
private SettingsManager settingsManagerInstance;
@@ -36,8 +43,8 @@ public static WatchFragment newInstance() {
return watchFragmentInstance;
}
- @Override public void onAttach(Context context) {
- super.onAttach(context);
+ @Override public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
settingsManagerInstance = new SettingsManager();
}
@@ -49,18 +56,7 @@ public static WatchFragment newInstance() {
super.onViewCreated(view, savedInstanceState);
buttonStartWatch = (Switch) view.findViewById(R.id.buttonStartWatch);
- buttonStartWatch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
- @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- if (settingsManagerInstance.isWatchEnabled() != isChecked) {
- if (! settingsManagerInstance.isWatchEnabled()) {
- settingsManagerInstance.enableWatch();
- } else {
- settingsManagerInstance.disableWatch();
- }
- updateUI();
- }
- }
- });
+ buttonStartWatch.setOnCheckedChangeListener(null);
buttonWatchInterval = (Button) view.findViewById(R.id.buttonWatchInterval);
buttonWatchInterval.setOnClickListener(new View.OnClickListener() {
@@ -102,10 +98,17 @@ public void onClick(View view) {
});
}
- @Override public void fragmentInvisible() {
+ @Override public void onPause() {
+ super.onPause();
}
- @Override public void fragmentVisible() {
+ @Override public void onResume() {
+ super.onResume();
+ if (settingsManagerInstance.isWatchEnabled()
+ && ! ApplicationInstance.canScheduleExactAlarms()) {
+ settingsManagerInstance.disableWatch();
+ }
+
updateUI();
}
@@ -125,6 +128,18 @@ public void onClick(View view) {
private void updateUI() {
buttonStartWatch.setChecked(
settingsManagerInstance.isWatchEnabled());
+ buttonStartWatch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ if (settingsManagerInstance.isWatchEnabled() != isChecked) {
+ if (! settingsManagerInstance.isWatchEnabled()) {
+ tryToEnableWatch();
+ } else {
+ settingsManagerInstance.disableWatch();
+ }
+ updateUI();
+ }
+ }
+ });
buttonWatchInterval.setText(
String.format(
@@ -150,4 +165,16 @@ private void updateUI() {
buttonWatchAnnouncementVibration.setClickable(! settingsManagerInstance.isWatchEnabled());
}
+ @TargetApi(Build.VERSION_CODES.S)
+ private void tryToEnableWatch() {
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
+ if (! ApplicationInstance.canScheduleExactAlarms()) {
+ Intent intent = new Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM);
+ startActivity(intent);
+ return;
+ }
+ }
+ settingsManagerInstance.enableWatch();
+ }
+
}
diff --git a/app/src/main/java/de/eric_scheibler/tactileclock/utils/ApplicationInstance.java b/app/src/main/java/de/eric_scheibler/tactileclock/utils/ApplicationInstance.java
index 4a99318..d895cc3 100644
--- a/app/src/main/java/de/eric_scheibler/tactileclock/utils/ApplicationInstance.java
+++ b/app/src/main/java/de/eric_scheibler/tactileclock/utils/ApplicationInstance.java
@@ -23,6 +23,8 @@
import android.annotation.SuppressLint;
import android.os.Handler;
import java.lang.Runnable;
+import java.util.Calendar;
+import android.os.SystemClock;
@@ -48,7 +50,8 @@ public class ApplicationInstance extends Application {
SettingsManager settingsManagerInstance = new SettingsManager();
boolean wasWatchEnabled = settingsManagerInstance.isWatchEnabled();
settingsManagerInstance.disableWatch();
- if (wasWatchEnabled) {
+ if (wasWatchEnabled
+ && ApplicationInstance.canScheduleExactAlarms()) {
settingsManagerInstance.enableWatch();
}
// update notivication
@@ -99,10 +102,51 @@ private void createNotificationChannel() {
*/
private static final int PENDING_INTENT_VIBRATE_TIME_ID = 39128;
+ /**
+ * SCHEDULE_EXACT_ALARM permission
+ * only required for android 12 (api 31 / S)
+ * on Android 13 onwards the implicitly granted permission USE_EXACT_ALARM is used and canScheduleExactAlarms() is always true
+ */
+ @TargetApi(Build.VERSION_CODES.S)
+ public static boolean canScheduleExactAlarms() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ return ((AlarmManager) ApplicationInstance.getContext().getSystemService(Context.ALARM_SERVICE)).canScheduleExactAlarms();
+ }
+ return true;
+ }
+
+ public boolean setAlarmAtFullHour(int hours) {
+ Calendar calendar = Calendar.getInstance();
+ // at full hour
+ calendar.setTimeInMillis(
+ System.currentTimeMillis() + hours*60*60*1000l);
+ calendar.set(Calendar.MINUTE, 0);
+ calendar.set(Calendar.SECOND, 0);
+ return setAlarm(calendar);
+ }
+
+ public boolean setAlarmAtFullMinute(int minutes) {
+ Calendar calendar = Calendar.getInstance();
+ // at full minute
+ calendar.setTimeInMillis(
+ System.currentTimeMillis() + minutes*60*1000l);
+ calendar.set(Calendar.SECOND, 0);
+ return setAlarm(calendar);
+ }
+
@TargetApi(Build.VERSION_CODES.KITKAT)
- public void setAlarm(long millisSinceDeviceStartup) {
+ @SuppressLint("MissingPermission")
+ private boolean setAlarm(Calendar calendar) {
+ if (! canScheduleExactAlarms()) {
+ return false;
+ }
+
+ long millisSinceDeviceStartup = SystemClock.elapsedRealtime()
+ + Math.abs(calendar.getTimeInMillis() - System.currentTimeMillis());
+
// create vibrate time pending intent
PendingIntent pendingIntent = createActionVibrateTimeAndSetNextAlarmPendingIntent();
+
// set alarm
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
alarmManager.setExactAndAllowWhileIdle(
@@ -114,6 +158,7 @@ public void setAlarm(long millisSinceDeviceStartup) {
alarmManager.set(
AlarmManager.ELAPSED_REALTIME_WAKEUP, millisSinceDeviceStartup, pendingIntent);
}
+ return true;
}
public void cancelAlarm() {
diff --git a/app/src/main/java/de/eric_scheibler/tactileclock/utils/ScreenReceiver.java b/app/src/main/java/de/eric_scheibler/tactileclock/utils/ScreenReceiver.java
index 764fef6..9b23f37 100644
--- a/app/src/main/java/de/eric_scheibler/tactileclock/utils/ScreenReceiver.java
+++ b/app/src/main/java/de/eric_scheibler/tactileclock/utils/ScreenReceiver.java
@@ -6,16 +6,19 @@
import androidx.core.content.ContextCompat;
import timber.log.Timber;
+import android.app.AlarmManager;
public class ScreenReceiver extends BroadcastReceiver {
@Override public void onReceive(Context context, Intent intent) {
- Timber.d("action = %1$s", intent.getAction());
+ Timber.d("onReceive: action = %1$s", intent.getAction());
+
if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
Intent updateNotificationIntent = new Intent(context, TactileClockService.class);
updateNotificationIntent.setAction(TactileClockService.ACTION_UPDATE_NOTIFICATION);
ContextCompat.startForegroundService(context, updateNotificationIntent);
+
} else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)
|| intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
Intent screenOnOffIntent = new Intent(context, TactileClockService.class);
diff --git a/app/src/main/java/de/eric_scheibler/tactileclock/utils/SettingsManager.java b/app/src/main/java/de/eric_scheibler/tactileclock/utils/SettingsManager.java
index 2c132e4..ee3bea5 100644
--- a/app/src/main/java/de/eric_scheibler/tactileclock/utils/SettingsManager.java
+++ b/app/src/main/java/de/eric_scheibler/tactileclock/utils/SettingsManager.java
@@ -1,9 +1,7 @@
package de.eric_scheibler.tactileclock.utils;
-import java.lang.Math;
-import java.util.Calendar;
import android.content.Context;
import android.content.Intent;
@@ -11,7 +9,6 @@
import android.content.SharedPreferences;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.os.SystemClock;
import android.preference.PreferenceManager;
@@ -33,6 +30,7 @@ public class SettingsManager {
private static final String KEY_RECENT_OPEN_TAB = "recentOpenTab";
private static final String KEY_HOUR_FORMAT = "hourFormat";
private static final String KEY_TIME_COMPONENT_ORDER = "timeComponentOrder";
+ private static final String KEY_MAX_STRENGTH_VIBRATIONS_ENABLED = "maxStrengthVibrationsEnabled";
// power button
private static final String KEY_POWER_BUTTON_SERVICE_ENABLED = "enableService";
private static final String KEY_POWER_BUTTON_ERROR_VIBRATION = "errorVibration";
@@ -50,6 +48,7 @@ public class SettingsManager {
// general settings
public static final boolean DEFAULT_FIRST_START = true;
public static final boolean DEFAULT_ASKED_FOR_NOTIFICATION_PERMISSION = false;
+ public static final boolean DEFAULT_MAX_STRENGTH_VIBRATIONS_ENABLED = true;
// power button
public static final boolean DEFAULT_POWER_BUTTON_SERVICE_ENABLED = true;
public static final boolean DEFAULT_POWER_BUTTON_ERROR_VIBRATION = true;
@@ -139,6 +138,18 @@ public void setTimeComponentOrder(TimeComponentOrder timeComponentOrder) {
editor.apply();
}
+ public boolean getMaxStrengthVibrationsEnabled() {
+ return settings.getBoolean(
+ KEY_MAX_STRENGTH_VIBRATIONS_ENABLED,
+ DEFAULT_MAX_STRENGTH_VIBRATIONS_ENABLED);
+ }
+
+ public void setMaxStrengthVibrationsEnabled(boolean enabled) {
+ Editor editor = settings.edit();
+ editor.putBoolean(KEY_MAX_STRENGTH_VIBRATIONS_ENABLED, enabled);
+ editor.apply();
+ }
+
/**
* power button
@@ -220,20 +231,13 @@ public boolean isWatchEnabled() {
public void enableWatch() {
setWatchEnabled(true);
// set first exact watch vibration alarm
- Calendar calendar = Calendar.getInstance();
if (this.getWatchStartAtNextFullHour()) {
// at next full hour
- calendar.setTimeInMillis(System.currentTimeMillis() + 60*60*1000l);
- calendar.set(Calendar.MINUTE, 0);
- calendar.set(Calendar.SECOND, 0);
+ ((ApplicationInstance) ApplicationInstance.getContext()).setAlarmAtFullHour(1);
} else {
// at next full minute
- calendar.setTimeInMillis(System.currentTimeMillis() + 60*1000l);
- calendar.set(Calendar.SECOND, 0);
+ ((ApplicationInstance) ApplicationInstance.getContext()).setAlarmAtFullMinute(1);
}
- ((ApplicationInstance) ApplicationInstance.getContext()).setAlarm(
- SystemClock.elapsedRealtime()
- + Math.abs(calendar.getTimeInMillis() - System.currentTimeMillis()));
}
public void disableWatch() {
diff --git a/app/src/main/java/de/eric_scheibler/tactileclock/utils/TactileClockService.java b/app/src/main/java/de/eric_scheibler/tactileclock/utils/TactileClockService.java
index 50db482..6d67b1c 100644
--- a/app/src/main/java/de/eric_scheibler/tactileclock/utils/TactileClockService.java
+++ b/app/src/main/java/de/eric_scheibler/tactileclock/utils/TactileClockService.java
@@ -1,5 +1,7 @@
package de.eric_scheibler.tactileclock.utils;
+ import android.os.Handler;
+ import android.os.Looper;
import java.util.Calendar;
import android.annotation.TargetApi;
@@ -17,7 +19,6 @@
import android.os.Build;
import android.os.IBinder;
-import android.os.SystemClock;
import android.os.Vibrator;
import androidx.core.app.NotificationCompat;
@@ -32,12 +33,15 @@
import android.content.pm.ServiceInfo;
import androidx.core.app.ServiceCompat;
import android.app.ForegroundServiceStartNotAllowedException;
+import android.os.VibrationEffect;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
public class TactileClockService extends Service {
// actions
public static final String ACTION_UPDATE_NOTIFICATION = "de.eric_scheibler.tactileclock.action.update_notification";
+ public static final String ACTION_VIBRATE_TIME = "de.eric_scheibler.tactileclock.action.vibrate_time";
public static final String ACTION_VIBRATE_TIME_AND_SET_NEXT_ALARM = "de.eric_scheibler.tactileclock.action.vibrate_time_and_set_next_alarm";
// vibrations
@@ -45,11 +49,18 @@ public class TactileClockService extends Service {
public static final long LONG_VIBRATION = 500;
public static final long ERROR_VIBRATION = 1000;
+ // amplitudes
+ public static final int AMPLITUDE_DEFAULT = 150;
+ public static final int AMPLITUDE_MAX = 250;
+
// gaps
public static final long SHORT_GAP = 250;
public static final long MEDIUM_GAP = 750;
public static final long LONG_GAP = 1250;
+ // broadcast responses
+ public static final String VIBRATION_FINISHED = "de.eric_scheibler.tactileclock.response.vibration_finished";
+
// service vars
private long lastActivation;
private ApplicationInstance applicationInstance;
@@ -68,6 +79,7 @@ public class TactileClockService extends Service {
notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
settingsManagerInstance = new SettingsManager();
vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
+
// register receiver that handles screen on and screen off logic
// can't be done in manifest
mScreenReceiver = new ScreenReceiver();
@@ -75,6 +87,11 @@ public class TactileClockService extends Service {
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
registerReceiver(mScreenReceiver, filter);
+
+ if (settingsManagerInstance.isWatchEnabled()
+ && ! ApplicationInstance.canScheduleExactAlarms()) {
+ settingsManagerInstance.disableWatch();
+ }
}
@Override public IBinder onBind(Intent intent) {
@@ -87,8 +104,7 @@ public class TactileClockService extends Service {
if (ACTION_UPDATE_NOTIFICATION.equals(intent.getAction())) {
startForegroundService();
- if (! settingsManagerInstance.getPowerButtonServiceEnabled()
- && ! settingsManagerInstance.isWatchEnabled()) {
+ if (shouldDestroyService()) {
destroyService();
}
@@ -102,7 +118,7 @@ public class TactileClockService extends Service {
// double click detected
// but screen was turned off and on instead of on and off
// vibrate error message
- vibrator.vibrate(ERROR_VIBRATION);
+ vibrateOnce(ERROR_VIBRATION);
}
lastActivation = System.currentTimeMillis();
@@ -119,6 +135,9 @@ public class TactileClockService extends Service {
}
lastActivation = System.currentTimeMillis();
+ } else if (ACTION_VIBRATE_TIME.equals(intent.getAction())) {
+ vibrateTime(false, false);
+
} else if (ACTION_VIBRATE_TIME_AND_SET_NEXT_ALARM.equals(intent.getAction())) {
// vibrate current time
if (this.isVibrationAllowed()) {
@@ -126,10 +145,10 @@ public class TactileClockService extends Service {
settingsManagerInstance.getWatchAnnouncementVibration(),
settingsManagerInstance.getWatchOnlyVibrateMinutes());
}
+
// set next alarm
- applicationInstance.setAlarm(
- SystemClock.elapsedRealtime()
- + settingsManagerInstance.getWatchVibrationIntervalInMinutes()*60*1000l);
+ applicationInstance.setAlarmAtFullMinute(
+ settingsManagerInstance.getWatchVibrationIntervalInMinutes());
}
}
}
@@ -161,17 +180,38 @@ private void startForegroundService() {
destroyService();
}
+ private boolean shouldDestroyService() {
+ return ! settingsManagerInstance.getPowerButtonServiceEnabled()
+ && ! settingsManagerInstance.isWatchEnabled();
+ }
+
private void destroyService() {
notificationManager.cancel(NOTIFICATION_ID);
stopForeground(true);
stopSelf();
}
+ @TargetApi(Build.VERSION_CODES.O)
+ public void vibrateOnce(long duration) {
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
+ vibrator.vibrate(
+ VibrationEffect.createOneShot(duration, getAmplitude()));
+ } else {
+ vibrator.vibrate(duration);
+ }
+ }
+
+ private int getAmplitude() {
+ return settingsManagerInstance.getMaxStrengthVibrationsEnabled()
+ ? AMPLITUDE_MAX : AMPLITUDE_DEFAULT;
+ }
+
/**
* vibration pattern functions
*/
+ @TargetApi(Build.VERSION_CODES.O)
private void vibrateTime(boolean announcementVibration, boolean minutesOnly) {
// get current time
int hours, minutes;
@@ -217,8 +257,32 @@ private void vibrateTime(boolean announcementVibration, boolean minutesOnly) {
pattern = concat(pattern, getVibrationPatternForMinutes(minutes));
}
+ // total duration
+ long totalDuration = 0l;
+ for (long duration : pattern) {
+ totalDuration += duration;
+ }
+
// start vibration
- vibrator.vibrate(pattern, -1);
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
+ int[] amplitudes = new int[pattern.length];
+ for (int i=0; i
-
+
+
Tactile Clock
+
+ Uhrzeit vibrieren
Menü öffnen
Menü schließen
@@ -74,6 +76,7 @@
Einstellungen
+ Mit maximaler Intensität vibrieren
Wähle zwischen 12 und 24 Stunden Zeitformat
12 Stunden Format
24 Stunden Format
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 930da0d..ddf9c5b 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -7,6 +7,8 @@
Tactile Clock
+
+ Vibrate time
Open menu
Close menu
@@ -70,6 +72,7 @@
Settings
+ Vibrate with maximal intensity
Choose between 12 and 24 hour time format
12 hour format
24 hour fourmat
diff --git a/app/src/main/res/xml/shortcuts.xml b/app/src/main/res/xml/shortcuts.xml
new file mode 100644
index 0000000..ab610ba
--- /dev/null
+++ b/app/src/main/res/xml/shortcuts.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+