334 lines
12 KiB
Java
334 lines
12 KiB
Java
/*
|
|
* Copyright (C) 2022 Paranoid Android
|
|
*
|
|
* 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
|
|
*
|
|
* http://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.
|
|
*/
|
|
|
|
package net.typeblog.lunatic.Manager;
|
|
|
|
import android.content.Context;
|
|
import android.media.audiofx.Visualizer;
|
|
import android.os.BatteryManager;
|
|
import android.util.Log;
|
|
|
|
import java.util.concurrent.ExecutorService;
|
|
import java.util.concurrent.Executors;
|
|
import java.util.concurrent.Future;
|
|
import java.util.concurrent.ThreadLocalRandom;
|
|
|
|
import net.typeblog.lunatic.Constants.Constants;
|
|
|
|
public final class AnimationManager {
|
|
|
|
private static final String TAG = "SpotlightAnimationManager";
|
|
private static final boolean DEBUG = true;
|
|
|
|
private Context mContext;
|
|
private LEDManager mLEDManager;
|
|
private BatteryManager mBatteryManager;
|
|
private Visualizer mVisualizer;
|
|
|
|
|
|
public AnimationManager(Context context) {
|
|
mContext = context;
|
|
|
|
mLEDManager = new LEDManager();
|
|
}
|
|
|
|
private static Future<?> submit(Runnable runnable) {
|
|
ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
|
|
return mExecutorService.submit(runnable);
|
|
}
|
|
|
|
private static boolean check(Constants.SpotlightMode mode) {
|
|
if (StatusManager.isCustomEffectActive()) {
|
|
if (DEBUG) Log.d(TAG, "Custom effects override all animations | name: " + mode.name());
|
|
return false;
|
|
}
|
|
|
|
switch (mode) {
|
|
case FLASHLIGHT:
|
|
if (!StatusManager.isAllLedsActive())
|
|
return false;
|
|
break;
|
|
case CALLS:
|
|
if (StatusManager.isAllLedsActive()) {
|
|
if (DEBUG) Log.d(TAG, "All LEDs are active, can\'t start animation | name: " + mode.name());
|
|
return false;
|
|
}
|
|
if (!StatusManager.isCallLedsActive())
|
|
return false;
|
|
break;
|
|
case MUSIC:
|
|
if (StatusManager.isAllLedsActive() || StatusManager.isCallLedsActive()) {
|
|
if (DEBUG) Log.d(TAG, "Call or All LEDs are active, can\'t start animation | name: " + mode.name());
|
|
return false;
|
|
}
|
|
if (!StatusManager.isMusicLedsActive())
|
|
return false;
|
|
break;
|
|
case NOTIFICATIONS:
|
|
if (StatusManager.isAllLedsActive() || StatusManager.isCallLedsActive() || StatusManager.isMusicLedsActive()) {
|
|
if (DEBUG) Log.d(TAG, "Call, Music or All LEDs are active, can\'t start animation | name: " + mode.name());
|
|
return false;
|
|
}
|
|
if (!StatusManager.isNotifLedsActive())
|
|
return false;
|
|
break;
|
|
case CHARGING:
|
|
if (StatusManager.isAllLedsActive() || StatusManager.isCallLedsActive() || StatusManager.isMusicLedsActive() || StatusManager.isNotifLedsActive()) {
|
|
if (DEBUG) Log.d(TAG, "Call, Music, Notification or All LEDs are active, can\'t start animation | name: " + mode.name());
|
|
return false;
|
|
}
|
|
if (!StatusManager.isChargingLedsActive())
|
|
return false;
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private void cleanupAndContinue() {
|
|
mLEDManager.enableAllLEDs(false);
|
|
|
|
if (StatusManager.isAllLedsActive()) {
|
|
playFlashlight();
|
|
} else if (StatusManager.isCallLedsActive()) {
|
|
playCall();
|
|
} else if (StatusManager.isMusicLedsActive()) {
|
|
playMusic();
|
|
} else if (StatusManager.isNotifLedsActive()) {
|
|
playNotifications();
|
|
} else if (StatusManager.isChargingLedsActive()) {
|
|
playCharging();
|
|
}
|
|
}
|
|
|
|
private int getBatteryLevel() {
|
|
mBatteryManager = (BatteryManager) mContext.getSystemService(Context.BATTERY_SERVICE);
|
|
|
|
return mBatteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
|
|
}
|
|
|
|
public void playCharging() {
|
|
StatusManager.setChargingLedsActive(true);
|
|
submit(() -> {
|
|
final int num_leds = mLEDManager.getNumLEDs();
|
|
int solid_leds = 0;
|
|
mLEDManager.enableAllLEDs(false);
|
|
mLEDManager.setColor(0xffffff);
|
|
mLEDManager.setBrightness((int) (Constants.BRIGHTNESS * 100));
|
|
try {
|
|
int oldBatteryLevel = -1;
|
|
while (check(Constants.SpotlightMode.CHARGING)) {
|
|
int batteryLevel = getBatteryLevel();
|
|
if (oldBatteryLevel != batteryLevel) {
|
|
solid_leds = (int) Math.floor(batteryLevel / 100.0d * num_leds);
|
|
for (int i = 0; i < solid_leds; i++) {
|
|
mLEDManager.enableLED(num_leds - i - 1, true);
|
|
Thread.sleep(150);
|
|
}
|
|
oldBatteryLevel = batteryLevel;
|
|
}
|
|
if (100 - solid_leds * mLEDManager.getNumLEDs() > 0) {
|
|
mLEDManager.enableLED(num_leds - solid_leds - 1, true);
|
|
Thread.sleep(500);
|
|
mLEDManager.enableLED(num_leds - solid_leds - 1, false);
|
|
Thread.sleep(500);
|
|
}
|
|
}
|
|
} catch (InterruptedException e) {
|
|
Log.e(TAG, "Error while playing charging animation", e);
|
|
}
|
|
mLEDManager.enableAllLEDs(false);
|
|
});
|
|
}
|
|
|
|
public void stopCharging() {
|
|
if (DEBUG) Log.d(TAG, "Done playing animation | name: charging");
|
|
StatusManager.setChargingLedsActive(false);
|
|
cleanupAndContinue();
|
|
}
|
|
|
|
public void playCall() {
|
|
StatusManager.setCallLedsActive(true);
|
|
|
|
submit(() -> {
|
|
mLEDManager.enableAllLEDs(false);
|
|
mLEDManager.setColor(0xffffff);
|
|
mLEDManager.setBrightness((int) (Constants.BRIGHTNESS * 100));
|
|
try {
|
|
boolean enableOdds = false;
|
|
while (check(Constants.SpotlightMode.CALLS)) {
|
|
for (int i = 0; i < mLEDManager.getNumLEDs(); i++) {
|
|
if (i % 2 == (enableOdds ? 1 : 0)) {
|
|
mLEDManager.enableLED(i, true);
|
|
} else {
|
|
mLEDManager.enableLED(i, false);
|
|
}
|
|
}
|
|
enableOdds = !enableOdds;
|
|
Thread.sleep(500);
|
|
}
|
|
} catch (InterruptedException e) {
|
|
Log.e(TAG, "Error while playing charging animation", e);
|
|
}
|
|
mLEDManager.enableAllLEDs(false);
|
|
});
|
|
}
|
|
|
|
public void stopCall() {
|
|
if (DEBUG) Log.d(TAG, "Disabling Call Animation");
|
|
StatusManager.setCallLedsActive(false);
|
|
cleanupAndContinue();
|
|
}
|
|
|
|
public void playNotifications() {
|
|
StatusManager.setNotifLedsActive(true);
|
|
if (DEBUG) Log.d(TAG, "Trying to play notification effect");
|
|
|
|
submit(() -> {
|
|
if (!check(Constants.SpotlightMode.NOTIFICATIONS))
|
|
return;
|
|
|
|
if (DEBUG) Log.d(TAG, "Playing notification effect");
|
|
|
|
try {
|
|
mLEDManager.setBrightness((int) (Constants.BRIGHTNESS * 100));
|
|
mLEDManager.setColor(0xffffff);
|
|
mLEDManager.enableAllLEDs(true);
|
|
Thread.sleep(100);
|
|
mLEDManager.enableAllLEDs(false);
|
|
Thread.sleep(60);
|
|
mLEDManager.enableAllLEDs(true);
|
|
Thread.sleep(100);
|
|
mLEDManager.enableAllLEDs(false);
|
|
} catch (InterruptedException e) {
|
|
mLEDManager.enableAllLEDs(false);
|
|
}
|
|
|
|
stopNotifications();
|
|
});
|
|
}
|
|
|
|
public void stopNotifications() {
|
|
if (DEBUG) Log.d(TAG, "Disabling Notifications Animation");
|
|
StatusManager.setNotifLedsActive(false);
|
|
cleanupAndContinue();
|
|
}
|
|
|
|
public void playFlashlight() {
|
|
StatusManager.setAllLedsActive(true);
|
|
|
|
submit(() -> {
|
|
if (check(Constants.SpotlightMode.FLASHLIGHT)) {
|
|
mLEDManager.setColor(0xffffff);
|
|
mLEDManager.setBrightness((int) (Constants.BRIGHTNESS * 100));
|
|
mLEDManager.enableAllLEDs(true);
|
|
}
|
|
});
|
|
}
|
|
|
|
public void stopFlashlight() {
|
|
if (DEBUG) Log.d(TAG, "Disabling Flashlight");
|
|
StatusManager.setAllLedsActive(false);
|
|
cleanupAndContinue();
|
|
}
|
|
|
|
private Visualizer.OnDataCaptureListener mVisualizerListener =
|
|
new Visualizer.OnDataCaptureListener() {
|
|
float rfk, ifk;
|
|
float dbValue;
|
|
float maxDbValue = 0;
|
|
float magnitude;
|
|
boolean isAnimating = false;
|
|
long lastColorChange = 0;
|
|
|
|
@Override
|
|
public void onWaveFormDataCapture(Visualizer visualizer, byte[] bytes, int samplingRate) {
|
|
}
|
|
|
|
@Override
|
|
public void onFftDataCapture(Visualizer visualizer, byte[] fft, int samplingRate) {
|
|
if (isAnimating) return;
|
|
if (!check(Constants.SpotlightMode.MUSIC))
|
|
return;
|
|
submit(() -> {
|
|
isAnimating = true;
|
|
int enabledLEDs = 0;
|
|
for (int i = 0; i < mLEDManager.getNumLEDs(); i++) {
|
|
rfk = fft[i * 2 + 2]/128.0f;
|
|
ifk = fft[i * 2 + 3]/128.0f;
|
|
magnitude = rfk * rfk + ifk * ifk;
|
|
dbValue = magnitude > 1 ? 1 : magnitude;
|
|
|
|
if (i == 0) {
|
|
// Use the lowest frequency component as the overall brightness
|
|
mLEDManager.setBrightness((int) (dbValue * Constants.BRIGHTNESS * 100));
|
|
}
|
|
|
|
int lightId = mLEDManager.getNumLEDs() - i - 1;
|
|
|
|
// Swap the ring with one of the smaller belts
|
|
// so that the ring represents the lowest frequencies
|
|
if (lightId == 4) {
|
|
lightId = 3;
|
|
} else if (lightId == 3) {
|
|
lightId = 4;
|
|
}
|
|
|
|
if (dbValue > 0.5) {
|
|
enabledLEDs++;
|
|
mLEDManager.enableLED(lightId, true);
|
|
} else {
|
|
mLEDManager.enableLED(lightId, false);
|
|
}
|
|
}
|
|
|
|
if (enabledLEDs == 0 && System.currentTimeMillis() - lastColorChange > 1000) {
|
|
mLEDManager.setColor(ThreadLocalRandom.current().nextInt(0xffffff));
|
|
lastColorChange = System.currentTimeMillis();
|
|
}
|
|
isAnimating = false;
|
|
});
|
|
}
|
|
};
|
|
|
|
public void playMusic() {
|
|
StatusManager.setMusicLedsActive(true);
|
|
|
|
try {
|
|
mVisualizer = new Visualizer(0);
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "error initializing visualizer", e);
|
|
return;
|
|
}
|
|
|
|
mVisualizer.setEnabled(false);
|
|
mVisualizer.setCaptureSize(66);
|
|
mVisualizer.setDataCaptureListener(mVisualizerListener, Visualizer.getMaxCaptureRate(),
|
|
false, true);
|
|
mVisualizer.setEnabled(true);
|
|
}
|
|
|
|
public void stopMusic() {
|
|
if (DEBUG) Log.d(TAG, "Disabling Music animation");
|
|
StatusManager.setMusicLedsActive(false);
|
|
if (mVisualizer != null) {
|
|
mVisualizer.setEnabled(false);
|
|
mVisualizer.release();
|
|
mVisualizer = null;
|
|
}
|
|
cleanupAndContinue();
|
|
}
|
|
}
|