commit a0170747c7382f496fdec037ed1f4bfee244d763 Author: AnierinB Date: Fri May 26 19:13:37 2023 -0700 PixelParts: Initial commit This branch will ONLY be configured for pixel 6 & 7 series. Co-authored-by: Pranav Vashi Co-authored-by: LorDClockaN Co-authored-by: AmeChanRain Co-authored-by: Ramii Ahmed Co-authored-by: Max Weninger Co-authored-by: Hikari-no-Tenshi Co-authored-by: EmanuelCN Signed-off-by: AnierinB diff --git a/Android.bp b/Android.bp new file mode 100644 index 0000000..4eb4110 --- /dev/null +++ b/Android.bp @@ -0,0 +1,35 @@ +// +// Copyright (C) 2023 The Evolution X Project +// SPDX-License-Identifier: Apache-2.0 +// + +android_app { + name: "PixelParts", + defaults: [ + "SettingsLibDefaults", + ], + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], + certificate: "platform", + platform_apis: true, + system_ext_specific: true, + privileged: true, + static_libs: [ + "androidx.core_core", + "androidx.preference_preference", + "particles", + ], + resource_dirs: ["res"], + optimize: { + proguard_flags_files: ["proguard.flags"], + }, +} + +java_import { + name: "particles", + jars: [ + "libs/LeonidsLib.jar", + ], +} diff --git a/AndroidManifest.xml b/AndroidManifest.xml new file mode 100644 index 0000000..ce966ba --- /dev/null +++ b/AndroidManifest.xml @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/crowdin.yml b/crowdin.yml new file mode 100644 index 0000000..dfe032a --- /dev/null +++ b/crowdin.yml @@ -0,0 +1,3 @@ +files: + - source: /**/values/strings.xml + translation: /%original_path%-%android_code%/%original_file_name% diff --git a/device.mk b/device.mk new file mode 100644 index 0000000..c3b0f41 --- /dev/null +++ b/device.mk @@ -0,0 +1,10 @@ +# PixelParts app +PRODUCT_PACKAGES += \ + PixelParts + +# PixelParts sepolicy +BOARD_SEPOLICY_DIRS += packages/apps/PixelParts/sepolicy + +# PixelParts init rc +PRODUCT_COPY_FILES += \ + packages/apps/PixelParts/init/init.pixelparts.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/init.pixelparts.rc diff --git a/init/init.pixelparts.rc b/init/init.pixelparts.rc new file mode 100644 index 0000000..0adbacc --- /dev/null +++ b/init/init.pixelparts.rc @@ -0,0 +1,13 @@ +on property:sys.boot_completed=1 + + # Power Efficient Workqueue + chown system system /sys/module/workqueue/parameters/power_efficient + chmod 0666 /sys/module/workqueue/parameters/power_efficient + + # HBM + chown system system /sys/class/backlight/panel0-backlight/hbm_mode + chmod 0666 /sys/class/backlight/panel0-backlight/hbm_mode + + # USB 2.0 Force Fast Charge + chown system system /sys/kernel/fast_charge/force_fast_charge + chmod 0666 /sys/kernel/fast_charge/force_fast_charge diff --git a/libs/LeonidsLib.jar b/libs/LeonidsLib.jar new file mode 100644 index 0000000..2713755 Binary files /dev/null and b/libs/LeonidsLib.jar differ diff --git a/proguard.flags b/proguard.flags new file mode 100644 index 0000000..3338089 --- /dev/null +++ b/proguard.flags @@ -0,0 +1,3 @@ +-keep class org.evolution.pixelparts.** { + *; +} diff --git a/res/drawable/easteregg.xml b/res/drawable/easteregg.xml new file mode 100644 index 0000000..9c6860c --- /dev/null +++ b/res/drawable/easteregg.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/res/drawable/ic_custom_seekbar_minus.xml b/res/drawable/ic_custom_seekbar_minus.xml new file mode 100644 index 0000000..5cb45db --- /dev/null +++ b/res/drawable/ic_custom_seekbar_minus.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/res/drawable/ic_custom_seekbar_plus.xml b/res/drawable/ic_custom_seekbar_plus.xml new file mode 100644 index 0000000..4646c77 --- /dev/null +++ b/res/drawable/ic_custom_seekbar_plus.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/res/drawable/ic_custom_seekbar_reset.xml b/res/drawable/ic_custom_seekbar_reset.xml new file mode 100644 index 0000000..662a13c --- /dev/null +++ b/res/drawable/ic_custom_seekbar_reset.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/res/drawable/ic_hbm_tile.xml b/res/drawable/ic_hbm_tile.xml new file mode 100644 index 0000000..7fa0adc --- /dev/null +++ b/res/drawable/ic_hbm_tile.xml @@ -0,0 +1,11 @@ + + + + diff --git a/res/drawable/ic_info_outline.xml b/res/drawable/ic_info_outline.xml new file mode 100644 index 0000000..4aeef10 --- /dev/null +++ b/res/drawable/ic_info_outline.xml @@ -0,0 +1,11 @@ + + + + diff --git a/res/drawable/ic_launcher_settings.xml b/res/drawable/ic_launcher_settings.xml new file mode 100644 index 0000000..9ede59d --- /dev/null +++ b/res/drawable/ic_launcher_settings.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/res/drawable/ic_pixel_parts.xml b/res/drawable/ic_pixel_parts.xml new file mode 100644 index 0000000..ad6effa --- /dev/null +++ b/res/drawable/ic_pixel_parts.xml @@ -0,0 +1,10 @@ + + + + diff --git a/res/drawable/ic_power_efficient_workqueue_tile.xml b/res/drawable/ic_power_efficient_workqueue_tile.xml new file mode 100644 index 0000000..adb9f1f --- /dev/null +++ b/res/drawable/ic_power_efficient_workqueue_tile.xml @@ -0,0 +1,11 @@ + + + + diff --git a/res/drawable/ic_usb2_fc_tile.xml b/res/drawable/ic_usb2_fc_tile.xml new file mode 100644 index 0000000..fbbc8fa --- /dev/null +++ b/res/drawable/ic_usb2_fc_tile.xml @@ -0,0 +1,11 @@ + + + + diff --git a/res/layout/preference_custom_seekbar.xml b/res/layout/preference_custom_seekbar.xml new file mode 100644 index 0000000..6532fba --- /dev/null +++ b/res/layout/preference_custom_seekbar.xml @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/rendering_jitter.xml b/res/layout/rendering_jitter.xml new file mode 100644 index 0000000..5ed5f3b --- /dev/null +++ b/res/layout/rendering_jitter.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + diff --git a/res/mipmap-hdpi/ic_launcher_settings.png b/res/mipmap-hdpi/ic_launcher_settings.png new file mode 100644 index 0000000..ddce98e Binary files /dev/null and b/res/mipmap-hdpi/ic_launcher_settings.png differ diff --git a/res/mipmap-mdpi/ic_launcher_settings.png b/res/mipmap-mdpi/ic_launcher_settings.png new file mode 100644 index 0000000..86f3be8 Binary files /dev/null and b/res/mipmap-mdpi/ic_launcher_settings.png differ diff --git a/res/mipmap-xhdpi/ic_launcher_settings.png b/res/mipmap-xhdpi/ic_launcher_settings.png new file mode 100644 index 0000000..360aa69 Binary files /dev/null and b/res/mipmap-xhdpi/ic_launcher_settings.png differ diff --git a/res/mipmap-xxhdpi/ic_launcher_settings.png b/res/mipmap-xxhdpi/ic_launcher_settings.png new file mode 100644 index 0000000..7cb67a9 Binary files /dev/null and b/res/mipmap-xxhdpi/ic_launcher_settings.png differ diff --git a/res/mipmap-xxxhdpi/ic_launcher_settings.png b/res/mipmap-xxxhdpi/ic_launcher_settings.png new file mode 100644 index 0000000..3fec9c4 Binary files /dev/null and b/res/mipmap-xxxhdpi/ic_launcher_settings.png differ diff --git a/res/values/attrs.xml b/res/values/attrs.xml new file mode 100644 index 0000000..56d15f5 --- /dev/null +++ b/res/values/attrs.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + diff --git a/res/values/colors.xml b/res/values/colors.xml new file mode 100644 index 0000000..9cec412 --- /dev/null +++ b/res/values/colors.xml @@ -0,0 +1,12 @@ + + + + + @*android:color/accent_device_default_light + #66000000 + + diff --git a/res/values/strings.xml b/res/values/strings.xml new file mode 100644 index 0000000..61489da --- /dev/null +++ b/res/values/strings.xml @@ -0,0 +1,48 @@ + + + + + + Pixel Parts + Configure device specific settings + Warning + Most of these features rely on specific kernel nodes in order to function. When flashing a custom kernel, you risk the possibility of losing access to some of these features. You have been warned! + OK + + + CPU + Power efficient workqueue + Save power by rescheduling work to a core that is already awake + + + Display + High brightness mode (HBM) + Enable peak luminance + Automatic HBM + Enable peak luminance based on sunlight + + threshold (lux) + Long time usage of High brightness mode may damage your display! + + + UiBench + Jitter + Calculate rendering jitter + + + USB + USB 2.0 fast charge + Enable CDP mode for fast charging on USB 2.0 ports + Not all USB 2.0 ports support CDP mode, use with caution! + + + Value: %s + by default + Default value: %s\nLong tap to set + Default value is set + + diff --git a/res/xml/main.xml b/res/xml/main.xml new file mode 100644 index 0000000..4327a5c --- /dev/null +++ b/res/xml/main.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sepolicy/file.te b/sepolicy/file.te new file mode 100644 index 0000000..e368c2c --- /dev/null +++ b/sepolicy/file.te @@ -0,0 +1,5 @@ +# USB 2.0 force fast charge +type sysfs_fastcharge, fs_type, sysfs_type; + +# Workqueue +type sysfs_workqueue, fs_type, sysfs_type; diff --git a/sepolicy/file_contexts b/sepolicy/file_contexts new file mode 100644 index 0000000..7b082c1 --- /dev/null +++ b/sepolicy/file_contexts @@ -0,0 +1,2 @@ +/sys/kernel/fast_charge/force_fast_charge u:object_r:sysfs_fastcharge:s0 +/sys/module/workqueue/parameters/power_efficient u:object_r:sysfs_workqueue:s0 diff --git a/sepolicy/pixelparts_app.te b/sepolicy/pixelparts_app.te new file mode 100644 index 0000000..5134a3f --- /dev/null +++ b/sepolicy/pixelparts_app.te @@ -0,0 +1,33 @@ +type pixelparts_app, domain; +typeattribute pixelparts_app mlstrustedsubject; + +app_domain(pixelparts_app) + +allow pixelparts_app { + activity_service + activity_task_service + audio_service + autofill_service + content_capture_service + game_service + gpu_service + hint_service + media_session_service + netstats_service + permission_checker_service + surfaceflinger_service +}:service_manager find; + +allow pixelparts_app system_app_data_file:dir create_dir_perms; +allow pixelparts_app system_app_data_file:{ file lnk_file } create_file_perms; +allow pixelparts_app system_data_file:dir search; + +allow pixelparts_app { + sysfs_fastcharge + sysfs_leds + sysfs_workqueue +}:file rw_file_perms; + +allow pixelparts_app { + sysfs_leds +}:dir r_dir_perms; diff --git a/sepolicy/seapp_contexts b/sepolicy/seapp_contexts new file mode 100644 index 0000000..7314d5e --- /dev/null +++ b/sepolicy/seapp_contexts @@ -0,0 +1 @@ +user=system seinfo=platform name=org.evolution.pixelparts domain=pixelparts_app type=system_app_data_file diff --git a/src/org/evolution/pixelparts/ConfigPanelSearchIndexablesProvider.java b/src/org/evolution/pixelparts/ConfigPanelSearchIndexablesProvider.java new file mode 100644 index 0000000..994ea80 --- /dev/null +++ b/src/org/evolution/pixelparts/ConfigPanelSearchIndexablesProvider.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2016 The CyanogenMod project + * 2017 The LineageOS Project + * 2023 The Evolution X Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.evolution.pixelparts; + +import android.database.Cursor; +import android.database.MatrixCursor; +import android.provider.SearchIndexableResource; +import android.provider.SearchIndexablesProvider; + +import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_CLASS_NAME; +import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_ICON_RESID; +import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_ACTION; +import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS; +import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE; +import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_RANK; +import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_RESID; +import static android.provider.SearchIndexablesContract.INDEXABLES_RAW_COLUMNS; +import static android.provider.SearchIndexablesContract.INDEXABLES_XML_RES_COLUMNS; +import static android.provider.SearchIndexablesContract.NON_INDEXABLES_KEYS_COLUMNS; + +public class ConfigPanelSearchIndexablesProvider extends SearchIndexablesProvider { + private static final String TAG = "ConfigPanelSearchIndexablesProvider"; + + public static final int SEARCH_IDX_BUTTON_PANEL = 0; + public static final int SEARCH_IDX_GESTURE_PANEL = 1; + public static final int SEARCH_IDX_OCLICK_PANEL = 2; + + private static SearchIndexableResource[] INDEXABLE_RES = new SearchIndexableResource[]{ + new SearchIndexableResource(1, R.xml.main, + PixelPartsActivity.class.getName(), + R.drawable.ic_pixel_parts), + }; + + @Override + public boolean onCreate() { + return true; + } + + @Override + public Cursor queryXmlResources(String[] projection) { + MatrixCursor cursor = new MatrixCursor(INDEXABLES_XML_RES_COLUMNS); + return cursor; + } + + private static Object[] generateResourceRef(SearchIndexableResource sir) { + Object[] ref = new Object[7]; + ref[COLUMN_INDEX_XML_RES_RANK] = sir.rank; + ref[COLUMN_INDEX_XML_RES_RESID] = sir.xmlResId; + ref[COLUMN_INDEX_XML_RES_CLASS_NAME] = null; + ref[COLUMN_INDEX_XML_RES_ICON_RESID] = sir.iconResId; + ref[COLUMN_INDEX_XML_RES_INTENT_ACTION] = "com.android.settings.action.EXTRA_SETTINGS"; + ref[COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE] = "org.evolution.pixelparts;"; + ref[COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS] = sir.className; + return ref; + } + + @Override + public Cursor queryRawData(String[] projection) { + MatrixCursor cursor = new MatrixCursor(INDEXABLES_RAW_COLUMNS); + return cursor; + } + + @Override + public Cursor queryNonIndexableKeys(String[] projection) { + MatrixCursor cursor = new MatrixCursor(NON_INDEXABLES_KEYS_COLUMNS); + return cursor; + } +} diff --git a/src/org/evolution/pixelparts/PixelParts.java b/src/org/evolution/pixelparts/PixelParts.java new file mode 100644 index 0000000..6f19bcc --- /dev/null +++ b/src/org/evolution/pixelparts/PixelParts.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2018-2022 crDroid Android Project + * 2023 The Evolution X Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.evolution.pixelparts; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.util.Log; +import androidx.preference.PreferenceFragment; +import androidx.preference.PreferenceManager; +import androidx.preference.Preference; +import androidx.preference.SwitchPreference; + +import org.evolution.pixelparts.misc.Constants; +import org.evolution.pixelparts.R; +import org.evolution.pixelparts.services.HBMService; +import org.evolution.pixelparts.utils.AutoHBMUtils; +import org.evolution.pixelparts.utils.Utils; + +public class PixelParts extends PreferenceFragment + implements Preference.OnPreferenceChangeListener { + private static final String TAG = PixelParts.class.getSimpleName(); + + // Power efficient workqueue switch + private SwitchPreference mPowerEfficientWorkqueueModeSwitch; + + // High brightness mode switches + private SwitchPreference mHBMSwitch; + private SwitchPreference mAutoHBMSwitch; + + // USB 2.0 fast charge switch + private SwitchPreference mUSB2FastChargeSwitch; + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + addPreferencesFromResource(R.xml.main); + SharedPreferences prefs = getActivity().getSharedPreferences("main", + Activity.MODE_PRIVATE); + if (savedInstanceState == null && !prefs.getBoolean("first_warning_shown", false)) { + showWarning(); + } + + SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getContext()); + Context context = getContext(); + + // Power efficient workqueue switch + mPowerEfficientWorkqueueModeSwitch = (SwitchPreference) findPreference(Constants.KEY_POWER_EFFICIENT_WORKQUEUE); + if (Utils.isFileWritable(Constants.NODE_POWER_EFFICIENT_WORKQUEUE)) { + mPowerEfficientWorkqueueModeSwitch.setEnabled(true); + mPowerEfficientWorkqueueModeSwitch.setChecked(sharedPrefs.getBoolean(Constants.KEY_POWER_EFFICIENT_WORKQUEUE, false)); + mPowerEfficientWorkqueueModeSwitch.setOnPreferenceChangeListener(this); + } else { + mPowerEfficientWorkqueueModeSwitch.setEnabled(false); + } + + // High brightness mode switches + mHBMSwitch = (SwitchPreference) findPreference(Constants.KEY_HBM); + mAutoHBMSwitch = (SwitchPreference) findPreference(Constants.KEY_AUTO_HBM); + if (Utils.isFileWritable(Constants.NODE_HBM)) { + mHBMSwitch.setEnabled(true); + mHBMSwitch.setChecked(sharedPrefs.getBoolean(Constants.KEY_HBM, false)); + mHBMSwitch.setOnPreferenceChangeListener(this); + mAutoHBMSwitch.setChecked(PreferenceManager.getDefaultSharedPreferences(getContext()).getBoolean(Constants.KEY_AUTO_HBM, false)); + mAutoHBMSwitch.setOnPreferenceChangeListener(this); + } else { + mHBMSwitch.setEnabled(false); + mAutoHBMSwitch.setEnabled(false); + } + + // USB 2.0 fast charge switch + mUSB2FastChargeSwitch = (SwitchPreference) findPreference(Constants.KEY_USB2_FAST_CHARGE); + if (Utils.isFileWritable(Constants.NODE_USB2_FAST_CHARGE)) { + mUSB2FastChargeSwitch.setEnabled(true); + mUSB2FastChargeSwitch.setChecked(sharedPrefs.getBoolean(Constants.KEY_USB2_FAST_CHARGE, false)); + mUSB2FastChargeSwitch.setOnPreferenceChangeListener(this); + } else { + mUSB2FastChargeSwitch.setEnabled(false); + } + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + // Power efficient workqueue switch + if (preference == mPowerEfficientWorkqueueModeSwitch) { + boolean enabled = (Boolean) newValue; + SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getContext()); + sharedPrefs.edit().putBoolean(Constants.KEY_POWER_EFFICIENT_WORKQUEUE, enabled).commit(); + Utils.writeValue(Constants.NODE_POWER_EFFICIENT_WORKQUEUE, enabled ? "1" : "0"); + return true; + // High brightness mode switch + } else if (preference == mHBMSwitch) { + boolean enabled = (Boolean) newValue; + SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getContext()); + sharedPrefs.edit().putBoolean(Constants.KEY_HBM, enabled).commit(); + Utils.writeValue(Constants.NODE_HBM, enabled ? "1" : "0"); + Intent hbmServiceIntent = new Intent(this.getContext(), HBMService.class); + if (enabled) { + this.getContext().startService(hbmServiceIntent); + } else { + this.getContext().stopService(hbmServiceIntent); + } + return true; + // Auto HBM switch + } else if (preference == mAutoHBMSwitch) { + Boolean enabled = (Boolean) newValue; + SharedPreferences.Editor prefChange = PreferenceManager.getDefaultSharedPreferences(getContext()).edit(); + prefChange.putBoolean(Constants.KEY_AUTO_HBM, enabled).commit(); + AutoHBMUtils.enableAutoHBM(getContext()); + return true; + // USB 2.0 fast charge switch + } else if (preference == mUSB2FastChargeSwitch) { + boolean enabled = (Boolean) newValue; + SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getContext()); + sharedPrefs.edit().putBoolean(Constants.KEY_USB2_FAST_CHARGE, enabled).commit(); + Utils.writeValue(Constants.NODE_USB2_FAST_CHARGE, enabled ? "1" : "0"); + return true; + } + + return false; + } + + public static boolean isAutoHBMEnabled(Context context) { + return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(Constants.KEY_AUTO_HBM, false); + } + + // Power efficient workqueue switch + public static void restorePowerEfficientWorkqueueSetting(Context context) { + if (Utils.isFileWritable(Constants.NODE_POWER_EFFICIENT_WORKQUEUE)) { + SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context); + boolean value = sharedPrefs.getBoolean(Constants.KEY_POWER_EFFICIENT_WORKQUEUE, false); + Utils.writeValue(Constants.NODE_POWER_EFFICIENT_WORKQUEUE, value ? "1" : "0"); + } + } + + // High brightness mode switch + public static void restoreHBMSetting(Context context) { + if (Utils.isFileWritable(Constants.NODE_HBM)) { + SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context); + boolean value = sharedPrefs.getBoolean(Constants.KEY_HBM, false); + Utils.writeValue(Constants.NODE_HBM, value ? "1" : "0"); + } + } + + // USB 2.0 fast charge switch + public static void restoreUSB2FastChargeSetting(Context context) { + if (Utils.isFileWritable(Constants.NODE_USB2_FAST_CHARGE)) { + SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context); + boolean value = sharedPrefs.getBoolean(Constants.KEY_USB2_FAST_CHARGE, false); + Utils.writeValue(Constants.NODE_USB2_FAST_CHARGE, value ? "1" : "0"); + } + } + + + // First launch warning dialog + public static class WarningDialogFragment extends DialogFragment { + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + return new AlertDialog.Builder(getActivity()) + .setTitle(R.string.pixel_parts_warning_title) + .setMessage(R.string.pixel_parts_warning_text) + .setNegativeButton(R.string.pixel_parts_dialog, (dialog, which) -> dialog.cancel()) + .create(); + } + + @Override + public void onCancel(DialogInterface dialog) { + getActivity().getSharedPreferences("main", Activity.MODE_PRIVATE) + .edit() + .putBoolean("first_warning_shown", true) + .commit(); + } + } + + private void showWarning() { + WarningDialogFragment fragment = new WarningDialogFragment(); + fragment.show(getFragmentManager(), "warning_dialog"); + } +} diff --git a/src/org/evolution/pixelparts/PixelPartsActivity.java b/src/org/evolution/pixelparts/PixelPartsActivity.java new file mode 100644 index 0000000..25100b8 --- /dev/null +++ b/src/org/evolution/pixelparts/PixelPartsActivity.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2023 The Evolution X Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.evolution.pixelparts; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.app.Fragment; +import android.content.DialogInterface; +import android.content.res.Resources; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import androidx.preference.PreferenceFragment; +import androidx.preference.PreferenceManager; + +import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity; +import com.android.settingslib.widget.R; + +import java.util.Arrays; +import java.util.Random; + +import com.plattysoft.leonids.ParticleSystem; + +import org.evolution.pixelparts.utils.ShakeUtils; + +public class PixelPartsActivity extends CollapsingToolbarBaseActivity + implements ShakeUtils.OnShakeListener { + + private static final String TAG = "PixelParts"; + + private ShakeUtils mShakeUtils; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + getFragmentManager().beginTransaction().replace(R.id.content_frame, + new PixelParts(), TAG).commit(); + + mShakeUtils = new ShakeUtils(this); + } + + @Override + protected void onResume() { + super.onResume(); + mShakeUtils.bindShakeListener(this); + } + + @Override + protected void onStop() { + super.onStop(); + mShakeUtils.unBindShakeListener(this); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mShakeUtils.unBindShakeListener(this); + } + + @Override + public void onShake(double speed) { + Random rand = new Random(); + int firstRandom = rand.nextInt(91-0); + int secondRandom = rand.nextInt(181-90)+90; + int thirdRandom = rand.nextInt(181-0); + + Drawable easteregg = getResources().getDrawable(R.drawable.easteregg,null); + int randomColor; + randomColor = Color.rgb( + Color.red(rand.nextInt(0xFFFFFF)), + Color.green(rand.nextInt(0xFFFFFF)), + Color.blue(rand.nextInt(0xFFFFFF))); + easteregg.setTint(randomColor); + + ParticleSystem ps = new ParticleSystem(this, 50, easteregg, 2000); + ps.setScaleRange(0.7f,1.3f); + ps.setSpeedRange(0.1f,0.25f); + ps.setAcceleration(0.0001f,thirdRandom); + ps.setRotationSpeedRange(firstRandom,secondRandom); + ps.setFadeOut(300); + ps.oneShot(this.findViewById(android.R.id.content),50); + } +} diff --git a/src/org/evolution/pixelparts/Startup.java b/src/org/evolution/pixelparts/Startup.java new file mode 100644 index 0000000..54a20fe --- /dev/null +++ b/src/org/evolution/pixelparts/Startup.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2018-2022 crDroid Android Project + * 2023 The Evolution X Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.evolution.pixelparts; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; + +public class Startup extends BroadcastReceiver { + + private static final String TAG = Startup.class.getSimpleName(); + + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + // PixelParts + PixelParts.restorePowerEfficientWorkqueueSetting(context); + PixelParts.restoreHBMSetting(context); + PixelParts.restoreUSB2FastChargeSetting(context); + } +} diff --git a/src/org/evolution/pixelparts/misc/Constants.java b/src/org/evolution/pixelparts/misc/Constants.java new file mode 100644 index 0000000..fc2fd91 --- /dev/null +++ b/src/org/evolution/pixelparts/misc/Constants.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 The Evolution X Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.evolution.pixelparts.misc; + +public class Constants { + + // Power efficient workqueue switch + public static final String KEY_POWER_EFFICIENT_WORKQUEUE = "power_efficient_workqueue"; + public static final String NODE_POWER_EFFICIENT_WORKQUEUE = "/sys/module/workqueue/parameters/power_efficient"; + + // High brightness mode switches + public static final String KEY_HBM = "hbm"; + public static final String KEY_AUTO_HBM = "auto_hbm"; + public static final String KEY_AUTO_HBM_THRESHOLD = "auto_hbm_threshold"; + public static final String NODE_HBM = "/sys/class/backlight/panel0-backlight/hbm_mode"; + + // USB 2.0 fast charge switch + public static final String KEY_USB2_FAST_CHARGE = "usb2_fast_charge"; + public static final String NODE_USB2_FAST_CHARGE = "/sys/kernel/fast_charge/force_fast_charge"; +} diff --git a/src/org/evolution/pixelparts/preferences/AutoHBMThresholdPreference.java b/src/org/evolution/pixelparts/preferences/AutoHBMThresholdPreference.java new file mode 100644 index 0000000..0e2de67 --- /dev/null +++ b/src/org/evolution/pixelparts/preferences/AutoHBMThresholdPreference.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2016 The OmniROM Project + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +package org.evolution.pixelparts.preferences; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.util.AttributeSet; +import android.util.Log; +import androidx.preference.Preference; +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceViewHolder; + +import org.evolution.pixelparts.misc.Constants; + +public class AutoHBMThresholdPreference extends CustomSeekBarPreference { + + private static int mMinVal = 2000; + private static int mMaxVal = 60000; + private static int mDefVal = 20000; + + public AutoHBMThresholdPreference(Context context, AttributeSet attrs) { + super(context, attrs); + + mInterval = 1000; + mShowSign = false; + mUnits = ""; + mContinuousUpdates = false; + mMinValue = mMinVal; + mMaxValue = mMaxVal; + mDefaultValueExists = true; + mDefaultValue = mDefVal; + SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getContext()); + mValue = Integer.parseInt(sharedPrefs.getString(Constants.KEY_AUTO_HBM_THRESHOLD, "20000")); + + setPersistent(false); + } + + @Override + protected void changeValue(int newValue) { + SharedPreferences.Editor prefChange = PreferenceManager.getDefaultSharedPreferences(getContext()).edit(); + prefChange.putString(Constants.KEY_AUTO_HBM_THRESHOLD, String.valueOf(newValue)).commit(); + } +} diff --git a/src/org/evolution/pixelparts/preferences/CustomSeekBarPreference.java b/src/org/evolution/pixelparts/preferences/CustomSeekBarPreference.java new file mode 100644 index 0000000..c0be0f4 --- /dev/null +++ b/src/org/evolution/pixelparts/preferences/CustomSeekBarPreference.java @@ -0,0 +1,357 @@ +/* + * Copyright (C) 2016-2022 crDroid Android Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.evolution.pixelparts.preferences; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.PorterDuff; +import androidx.core.content.res.TypedArrayUtils; +import androidx.preference.*; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.widget.ImageView; +import android.widget.SeekBar; +import android.widget.TextView; +import android.widget.Toast; + +import org.evolution.pixelparts.R; + +public class CustomSeekBarPreference extends Preference implements SeekBar.OnSeekBarChangeListener { + protected final String TAG = getClass().getName(); + private static final String SETTINGS_NS = "http://schemas.android.com/apk/res/com.android.settings"; + protected static final String ANDROIDNS = "http://schemas.android.com/apk/res/android"; + + protected int mInterval = 1; + protected boolean mShowSign = false; + protected String mUnits = ""; + protected boolean mContinuousUpdates = false; + + protected int mMinValue = 0; + protected int mMaxValue = 100; + protected boolean mDefaultValueExists = false; + protected int mDefaultValue; + protected boolean mDefaultValueTextExists = false; + protected String mDefaultValueText; + + protected int mValue; + + protected TextView mValueTextView; + protected ImageView mResetImageView; + protected ImageView mMinusImageView; + protected ImageView mPlusImageView; + protected SeekBar mSeekBar; + + protected boolean mTrackingTouch = false; + protected int mTrackingValue; + + public CustomSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomSeekBarPreference); + try { + mShowSign = a.getBoolean(R.styleable.CustomSeekBarPreference_showSign, mShowSign); + String units = a.getString(R.styleable.CustomSeekBarPreference_units); + if (units != null) + mUnits = " " + units; + mContinuousUpdates = a.getBoolean(R.styleable.CustomSeekBarPreference_continuousUpdates, mContinuousUpdates); + String defaultValueText = a.getString(R.styleable.CustomSeekBarPreference_defaultValueText); + mDefaultValueTextExists = defaultValueText != null && !defaultValueText.isEmpty(); + if (mDefaultValueTextExists) { + mDefaultValueText = defaultValueText; + } + } finally { + a.recycle(); + } + + try { + String newInterval = attrs.getAttributeValue(SETTINGS_NS, "interval"); + if (newInterval != null) + mInterval = Integer.parseInt(newInterval); + } catch (Exception e) { + Log.e(TAG, "Invalid interval value", e); + } + mMinValue = attrs.getAttributeIntValue(SETTINGS_NS, "min", mMinValue); + mMaxValue = attrs.getAttributeIntValue(ANDROIDNS, "max", mMaxValue); + if (mMaxValue < mMinValue) + mMaxValue = mMinValue; + String defaultValue = attrs.getAttributeValue(ANDROIDNS, "defaultValue"); + mDefaultValueExists = defaultValue != null && !defaultValue.isEmpty(); + if (mDefaultValueExists) { + mDefaultValue = getLimitedValue(Integer.parseInt(defaultValue)); + mValue = mDefaultValue; + } else { + mValue = mMinValue; + } + + mSeekBar = new SeekBar(context, attrs); + setLayoutResource(R.layout.preference_custom_seekbar); + } + + public CustomSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public CustomSeekBarPreference(Context context, AttributeSet attrs) { + this(context, attrs, TypedArrayUtils.getAttr(context, + androidx.preference.R.attr.preferenceStyle, + android.R.attr.preferenceStyle)); + } + + public CustomSeekBarPreference(Context context) { + this(context, null); + } + + @Override + public void onDependencyChanged(Preference dependency, boolean disableDependent) { + super.onDependencyChanged(dependency, disableDependent); + this.setShouldDisableView(true); + if (mSeekBar != null) + mSeekBar.setEnabled(!disableDependent); + if (mResetImageView != null) + mResetImageView.setEnabled(!disableDependent); + if (mPlusImageView != null) + mPlusImageView.setEnabled(!disableDependent); + if (mMinusImageView != null) + mMinusImageView.setEnabled(!disableDependent); + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + try + { + // move our seekbar to the new view we've been given + ViewParent oldContainer = mSeekBar.getParent(); + ViewGroup newContainer = (ViewGroup) holder.findViewById(R.id.seekbar); + if (oldContainer != newContainer) { + // remove the seekbar from the old view + if (oldContainer != null) { + ((ViewGroup) oldContainer).removeView(mSeekBar); + } + // remove the existing seekbar (there may not be one) and add ours + newContainer.removeAllViews(); + newContainer.addView(mSeekBar, ViewGroup.LayoutParams.FILL_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + } + } catch (Exception ex) { + Log.e(TAG, "Error binding view: " + ex.toString()); + } + + mSeekBar.setMax(getSeekValue(mMaxValue)); + mSeekBar.setProgress(getSeekValue(mValue)); + mSeekBar.setEnabled(isEnabled()); + + mValueTextView = (TextView) holder.findViewById(R.id.value); + mResetImageView = (ImageView) holder.findViewById(R.id.reset); + mMinusImageView = (ImageView) holder.findViewById(R.id.minus); + mPlusImageView = (ImageView) holder.findViewById(R.id.plus); + + updateValueViews(); + + mSeekBar.setOnSeekBarChangeListener(this); + mResetImageView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Toast.makeText(getContext(), getContext().getString(R.string.custom_seekbar_default_value_to_set, getTextValue(mDefaultValue)), + Toast.LENGTH_LONG).show(); + } + }); + mResetImageView.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View view) { + setValue(mDefaultValue, true); + return true; + } + }); + mMinusImageView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + setValue(mValue - mInterval, true); + } + }); + mMinusImageView.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View view) { + setValue(mMaxValue - mMinValue > mInterval * 2 && mMaxValue + mMinValue < mValue * 2 ? Math.floorDiv(mMaxValue + mMinValue, 2) : mMinValue, true); + return true; + } + }); + mPlusImageView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + setValue(mValue + mInterval, true); + } + }); + mPlusImageView.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View view) { + setValue(mMaxValue - mMinValue > mInterval * 2 && mMaxValue + mMinValue > mValue * 2 ? -1 * Math.floorDiv(-1 * (mMaxValue + mMinValue), 2) : mMaxValue, true); + return true; + } + }); + } + + protected int getLimitedValue(int v) { + return v < mMinValue ? mMinValue : (v > mMaxValue ? mMaxValue : v); + } + + protected int getSeekValue(int v) { + return 0 - Math.floorDiv(mMinValue - v, mInterval); + } + + protected String getTextValue(int v) { + if (mDefaultValueTextExists && mDefaultValueExists && v == mDefaultValue) { + return mDefaultValueText; + } + return (mShowSign && v > 0 ? "+" : "") + String.valueOf(v) + mUnits; + } + + protected void updateValueViews() { + if (mValueTextView != null) { + if (!mTrackingTouch || mContinuousUpdates) { + if (mDefaultValueTextExists && mDefaultValueExists && mValue == mDefaultValue) { + mValueTextView.setText(mDefaultValueText + " (" + + getContext().getString(R.string.custom_seekbar_default_value) + ")"); + } else { + mValueTextView.setText(getContext().getString(R.string.custom_seekbar_value, getTextValue(mValue)) + + (mDefaultValueExists && mValue == mDefaultValue ? " (" + + getContext().getString(R.string.custom_seekbar_default_value) + ")" : "")); + } + } else { + if (mDefaultValueTextExists && mDefaultValueExists && mTrackingValue == mDefaultValue) { + mValueTextView.setText("[" + mDefaultValueText + "]"); + } else { + mValueTextView.setText(getContext().getString(R.string.custom_seekbar_value, "[" + getTextValue(mTrackingValue) + "]")); + } + } + } + if (mResetImageView != null) { + if (!mDefaultValueExists || mValue == mDefaultValue || mTrackingTouch) + mResetImageView.setVisibility(View.INVISIBLE); + else + mResetImageView.setVisibility(View.VISIBLE); + } + if (mMinusImageView != null) { + if (mValue == mMinValue || mTrackingTouch) { + mMinusImageView.setClickable(false); + mMinusImageView.setColorFilter(getContext().getColor(R.color.disabled_text_color), + PorterDuff.Mode.MULTIPLY); + } else { + mMinusImageView.setClickable(true); + mMinusImageView.clearColorFilter(); + } + } + if (mPlusImageView != null) { + if (mValue == mMaxValue || mTrackingTouch) { + mPlusImageView.setClickable(false); + mPlusImageView.setColorFilter(getContext().getColor(R.color.disabled_text_color), PorterDuff.Mode.MULTIPLY); + } else { + mPlusImageView.setClickable(true); + mPlusImageView.clearColorFilter(); + } + } + } + + protected void changeValue(int newValue) { + // for subclasses + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + int newValue = getLimitedValue(mMinValue + (progress * mInterval)); + if (mTrackingTouch && !mContinuousUpdates) { + mTrackingValue = newValue; + updateValueViews(); + } else if (mValue != newValue) { + // change rejected, revert to the previous value + if (!callChangeListener(newValue)) { + mSeekBar.setProgress(getSeekValue(mValue)); + return; + } + // change accepted, store it + changeValue(newValue); + persistInt(newValue); + + mValue = newValue; + updateValueViews(); + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + mTrackingValue = mValue; + mTrackingTouch = true; + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + mTrackingTouch = false; + if (!mContinuousUpdates) + onProgressChanged(mSeekBar, getSeekValue(mTrackingValue), false); + notifyChanged(); + } + + @Override + protected void onSetInitialValue(boolean restoreValue, Object defaultValue) { + if (restoreValue) + mValue = getPersistedInt(mValue); + } + + @Override + public void setDefaultValue(Object defaultValue) { + if (defaultValue instanceof Integer) + setDefaultValue((Integer) defaultValue, mSeekBar != null); + else + setDefaultValue(defaultValue == null ? (String) null : defaultValue.toString(), mSeekBar != null); + } + + public void setDefaultValue(int newValue, boolean update) { + newValue = getLimitedValue(newValue); + if (!mDefaultValueExists || mDefaultValue != newValue) { + mDefaultValueExists = true; + mDefaultValue = newValue; + if (update) + updateValueViews(); + } + } + + public void setDefaultValue(String newValue, boolean update) { + if (mDefaultValueExists && (newValue == null || newValue.isEmpty())) { + mDefaultValueExists = false; + if (update) + updateValueViews(); + } else if (newValue != null && !newValue.isEmpty()) { + setDefaultValue(Integer.parseInt(newValue), update); + } + } + + public void setValue(int newValue) { + mValue = getLimitedValue(newValue); + if (mSeekBar != null) mSeekBar.setProgress(getSeekValue(mValue)); + } + + public void setValue(int newValue, boolean update) { + newValue = getLimitedValue(newValue); + if (mValue != newValue) { + if (update) + mSeekBar.setProgress(getSeekValue(newValue)); + else + mValue = newValue; + } + } + + public int getValue() { + return mValue; + } + + public void refresh(int newValue) { + // this will ... + setValue(newValue, mSeekBar != null); + } +} diff --git a/src/org/evolution/pixelparts/services/AutoHBMService.java b/src/org/evolution/pixelparts/services/AutoHBMService.java new file mode 100644 index 0000000..da8f4a1 --- /dev/null +++ b/src/org/evolution/pixelparts/services/AutoHBMService.java @@ -0,0 +1,125 @@ +package org.evolution.pixelparts.services; + +import android.app.KeyguardManager; +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.os.IBinder; +import android.os.PowerManager; +import androidx.preference.PreferenceManager; + +import org.evolution.pixelparts.misc.Constants; +import org.evolution.pixelparts.utils.Utils; + +public class AutoHBMService extends Service { + + private static boolean mAutoHBMActive = false; + + private SensorManager mSensorManager; + Sensor mLightSensor; + + private SharedPreferences mSharedPrefs; + + public void activateLightSensorRead() { + mSensorManager = (SensorManager) getApplicationContext().getSystemService(Context.SENSOR_SERVICE); + mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); + mSensorManager.registerListener(mSensorEventListener, mLightSensor, SensorManager.SENSOR_DELAY_NORMAL); + } + + public void deactivateLightSensorRead() { + mSensorManager.unregisterListener(mSensorEventListener); + mAutoHBMActive = false; + enableHBM(false); + } + + private void enableHBM(boolean enable) { + if (enable) { + Utils.writeValue(Constants.NODE_HBM, "1"); + } else { + Utils.writeValue(Constants.NODE_HBM, "0"); + } + } + + private boolean isCurrentlyEnabled() { + String fileValue = Utils.getFileValue(Constants.NODE_HBM, "0"); + return fileValue.equals("1") ? true : false; + } + + SensorEventListener mSensorEventListener = new SensorEventListener() { + @Override + public void onSensorChanged(SensorEvent event) { + float lux = event.values[0]; + KeyguardManager km = + (KeyguardManager) getSystemService(getApplicationContext().KEYGUARD_SERVICE); + boolean keyguardShowing = km.inKeyguardRestrictedInputMode(); + float threshold = Float.parseFloat(mSharedPrefs.getString(Constants.KEY_AUTO_HBM_THRESHOLD, "20000")); + if (lux > threshold) { + if ((!mAutoHBMActive | !isCurrentlyEnabled()) && !keyguardShowing) { + mAutoHBMActive = true; + enableHBM(true); + } + } + if (lux < threshold) { + if (mAutoHBMActive) { + mAutoHBMActive = false; + enableHBM(false); + } + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + // do nothing + } + }; + + private BroadcastReceiver mScreenStateReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) { + activateLightSensorRead(); + } else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) { + deactivateLightSensorRead(); + } + } + }; + + @Override + public void onCreate() { + IntentFilter screenStateFilter = new IntentFilter(Intent.ACTION_SCREEN_ON); + screenStateFilter.addAction(Intent.ACTION_SCREEN_OFF); + registerReceiver(mScreenStateReceiver, screenStateFilter); + mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); + if (pm.isInteractive()) { + activateLightSensorRead(); + } + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + return START_STICKY; + } + + @Override + public void onDestroy() { + super.onDestroy(); + unregisterReceiver(mScreenStateReceiver); + PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); + if (pm.isInteractive()) { + deactivateLightSensorRead(); + } + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } +} diff --git a/src/org/evolution/pixelparts/services/HBMService.java b/src/org/evolution/pixelparts/services/HBMService.java new file mode 100644 index 0000000..56241ee --- /dev/null +++ b/src/org/evolution/pixelparts/services/HBMService.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2023 The Evolution X Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.evolution.pixelparts.services; + +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.os.IBinder; +import android.preference.PreferenceManager; + +import org.evolution.pixelparts.misc.Constants; + +public class HBMService extends Service { + + private BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(Intent.ACTION_SCREEN_OFF)) { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); + SharedPreferences.Editor editor = preferences.edit(); + editor.putBoolean(Constants.KEY_HBM, false); + editor.commit(); + stopSelf(); + } + } + }; + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + IntentFilter intentFilter = new IntentFilter(Intent.ACTION_SCREEN_OFF); + registerReceiver(mReceiver, intentFilter); + return START_REDELIVER_INTENT; + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public void onDestroy() { + unregisterReceiver(mReceiver); + super.onDestroy(); + } +} diff --git a/src/org/evolution/pixelparts/services/HBMTileService.java b/src/org/evolution/pixelparts/services/HBMTileService.java new file mode 100644 index 0000000..5017eb2 --- /dev/null +++ b/src/org/evolution/pixelparts/services/HBMTileService.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2023 The Evolution X Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.evolution.pixelparts.services; + +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.content.BroadcastReceiver; +import android.service.quicksettings.Tile; +import android.service.quicksettings.TileService; +import androidx.preference.PreferenceManager; + +import org.evolution.pixelparts.misc.Constants; +import org.evolution.pixelparts.utils.Utils; + +public class HBMTileService extends TileService { + + private BroadcastReceiver screenStateReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) { + SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context); + sharedPrefs.edit().putBoolean(Constants.KEY_HBM, false).commit(); + updateTile(false); + } + } + }; + + private void updateTile(boolean enabled) { + final Tile tile = getQsTile(); + tile.setState(enabled ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE); + tile.updateTile(); + } + + + @Override + public void onCreate() { + super.onCreate(); + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_SCREEN_OFF); + registerReceiver(screenStateReceiver, filter); + } + + @Override + public void onDestroy() { + super.onDestroy(); + unregisterReceiver(screenStateReceiver); + } + + @Override + public void onStartListening() { + super.onStartListening(); + SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); + updateTile(sharedPrefs.getBoolean(Constants.KEY_HBM, false)); + } + + @Override + public void onStopListening() { + super.onStopListening(); + } + + @Override + public void onClick() { + super.onClick(); + SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); + final boolean enabled = !(sharedPrefs.getBoolean(Constants.KEY_HBM, false)); + Utils.writeValue(Constants.NODE_HBM, enabled ? "1" : "0"); + sharedPrefs.edit().putBoolean(Constants.KEY_HBM, enabled).commit(); + updateTile(enabled); + } +} diff --git a/src/org/evolution/pixelparts/services/PEWQTileSerice.java b/src/org/evolution/pixelparts/services/PEWQTileSerice.java new file mode 100644 index 0000000..2d1f343 --- /dev/null +++ b/src/org/evolution/pixelparts/services/PEWQTileSerice.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 The Evolution X Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.evolution.pixelparts.services; + +import android.content.SharedPreferences; +import android.service.quicksettings.Tile; +import android.service.quicksettings.TileService; +import androidx.preference.PreferenceManager; + +import org.evolution.pixelparts.misc.Constants; +import org.evolution.pixelparts.utils.Utils; + +public class PEWQTileSerice extends TileService { + + private void updateTile(boolean enabled) { + final Tile tile = getQsTile(); + tile.setState(enabled ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE); + tile.updateTile(); + } + + @Override + public void onStartListening() { + super.onStartListening(); + SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); + updateTile(sharedPrefs.getBoolean(Constants.KEY_POWER_EFFICIENT_WORKQUEUE, false)); + } + + @Override + public void onStopListening() { + super.onStopListening(); + } + + @Override + public void onClick() { + super.onClick(); + SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); + final boolean enabled = !(sharedPrefs.getBoolean(Constants.KEY_POWER_EFFICIENT_WORKQUEUE, false)); + Utils.writeValue(Constants.NODE_POWER_EFFICIENT_WORKQUEUE, enabled ? "1" : "0"); + sharedPrefs.edit().putBoolean(Constants.KEY_POWER_EFFICIENT_WORKQUEUE, enabled).commit(); + updateTile(enabled); + } +} diff --git a/src/org/evolution/pixelparts/services/USB2FCTileService.java b/src/org/evolution/pixelparts/services/USB2FCTileService.java new file mode 100644 index 0000000..e7e9d45 --- /dev/null +++ b/src/org/evolution/pixelparts/services/USB2FCTileService.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 The Evolution X Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.evolution.pixelparts.services; + +import android.content.SharedPreferences; +import android.service.quicksettings.Tile; +import android.service.quicksettings.TileService; +import androidx.preference.PreferenceManager; + +import org.evolution.pixelparts.misc.Constants; +import org.evolution.pixelparts.utils.Utils; + +public class USB2FCTileService extends TileService { + + private void updateTile(boolean enabled) { + final Tile tile = getQsTile(); + tile.setState(enabled ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE); + tile.updateTile(); + } + + @Override + public void onStartListening() { + super.onStartListening(); + SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); + updateTile(sharedPrefs.getBoolean(Constants.KEY_USB2_FAST_CHARGE, false)); + } + + @Override + public void onStopListening() { + super.onStopListening(); + } + + @Override + public void onClick() { + super.onClick(); + SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); + final boolean enabled = !(sharedPrefs.getBoolean(Constants.KEY_USB2_FAST_CHARGE, false)); + Utils.writeValue(Constants.NODE_USB2_FAST_CHARGE, enabled ? "1" : "0"); + sharedPrefs.edit().putBoolean(Constants.KEY_USB2_FAST_CHARGE, enabled).commit(); + updateTile(enabled); + } +} diff --git a/src/org/evolution/pixelparts/uibench/JitterTestActivity.java b/src/org/evolution/pixelparts/uibench/JitterTestActivity.java new file mode 100644 index 0000000..676dbaf --- /dev/null +++ b/src/org/evolution/pixelparts/uibench/JitterTestActivity.java @@ -0,0 +1,357 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +package org.evolution.pixelparts.uibench; + +import android.app.Activity; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Message; +import android.util.AttributeSet; +import android.view.FrameMetrics; +import android.view.View; +import android.view.Window; +import android.view.Window.OnFrameMetricsAvailableListener; +import android.view.animation.AnimationUtils; +import android.widget.TextView; + +import org.evolution.pixelparts.R; + +public class JitterTestActivity extends Activity { + private TextView mJitterReport; + private TextView mUiFrameTimeReport; + private TextView mRenderThreadTimeReport; + private TextView mTotalFrameTimeReport; + private TextView mMostlyTotalFrameTimeReport; + private PointGraphView mGraph; + + private static Handler sMetricsHandler; + static { + HandlerThread thread = new HandlerThread("frameMetricsListener"); + thread.start(); + sMetricsHandler = new Handler(thread.getLooper()); + } + + private Handler mUpdateHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case R.id.jitter_mma: + mJitterReport.setText((CharSequence) msg.obj); + break; + case R.id.totalish_mma: + mMostlyTotalFrameTimeReport.setText((CharSequence) msg.obj); + break; + case R.id.ui_frametime_mma: + mUiFrameTimeReport.setText((CharSequence) msg.obj); + break; + case R.id.rt_frametime_mma: + mRenderThreadTimeReport.setText((CharSequence) msg.obj); + break; + case R.id.total_mma: + mTotalFrameTimeReport.setText((CharSequence) msg.obj); + break; + case R.id.graph: + mGraph.addJitterSample(msg.arg1, msg.arg2); + break; + } + } + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.rendering_jitter); + View content = findViewById(android.R.id.content); + content.setBackground(new AnimatedBackgroundDrawable()); + content.setKeepScreenOn(true); + mJitterReport = findViewById(R.id.jitter_mma); + mMostlyTotalFrameTimeReport = findViewById(R.id.totalish_mma); + mUiFrameTimeReport = findViewById(R.id.ui_frametime_mma); + mRenderThreadTimeReport = findViewById(R.id.rt_frametime_mma); + mTotalFrameTimeReport = findViewById(R.id.total_mma); + mGraph = findViewById(R.id.graph); + mJitterReport.setText("abcdefghijklmnopqrstuvwxyz"); + mMostlyTotalFrameTimeReport.setText("ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + mUiFrameTimeReport.setText("0123456789"); + mRenderThreadTimeReport.setText(",.!()[]{};"); + getWindow().addOnFrameMetricsAvailableListener(mMetricsListener, sMetricsHandler); + } + + public static final class PointGraphView extends View { + private static final float[] JITTER_LINES_MS = { + .5f, 1.0f, 1.5f, 2.0f, 3.0f, 4.0f, 5.0f + }; + private static final String[] JITTER_LINES_LABELS = makeLabels(JITTER_LINES_MS); + private static final int[] JITTER_LINES_COLORS = new int[] { + 0xFF00E676, 0xFFFFF176, 0xFFFDD835, 0xFFFBC02D, 0xFFF9A825, + 0xFFF57F17, 0xFFDD2C00 + }; + private Paint mPaint = new Paint(); + private float[] mJitterYs = new float[JITTER_LINES_MS.length]; + private float mLabelWidth; + private float mLabelHeight; + private float mDensity; + private float mGraphScale; + private float mGraphMaxMs; + + private float[] mJitterPoints; + private float[] mJitterAvgPoints; + + public PointGraphView(Context context, AttributeSet attrs) { + super(context, attrs); + setWillNotDraw(false); + mDensity = context.getResources().getDisplayMetrics().density; + mPaint.setTextSize(dp(10)); + Rect textBounds = new Rect(); + mPaint.getTextBounds("8.8", 0, 3, textBounds); + mLabelWidth = textBounds.width() + dp(2); + mLabelHeight = textBounds.height(); + } + + public void addJitterSample(int jitterUs, int jitterUsAvg) { + for (int i = 1; i < mJitterPoints.length - 2; i += 2) { + mJitterPoints[i] = mJitterPoints[i + 2]; + mJitterAvgPoints[i] = mJitterAvgPoints[i + 2]; + } + mJitterPoints[mJitterPoints.length - 1] = + getHeight() - mGraphScale * (jitterUs / 1000.0f); + mJitterAvgPoints[mJitterAvgPoints.length - 1] = + getHeight() - mGraphScale * (jitterUsAvg / 1000.0f); + invalidate(); + } + + private float dp(float dp) { + return mDensity * dp; + } + + @Override + protected void onDraw(Canvas canvas) { + canvas.drawColor(0x90000000); + int h = getHeight(); + int w = getWidth(); + mPaint.setColor(Color.WHITE); + mPaint.setStrokeWidth(dp(1)); + canvas.drawLine(mLabelWidth, 0, mLabelWidth, h, mPaint); + for (int i = 0; i < JITTER_LINES_LABELS.length; i++) { + canvas.drawText(JITTER_LINES_LABELS[i], + 0, (float) Math.floor(mJitterYs[i] + mLabelHeight * .5f), mPaint); + } + for (int i = 0; i < JITTER_LINES_LABELS.length; i++) { + mPaint.setColor(JITTER_LINES_COLORS[i]); + canvas.drawLine(mLabelWidth, mJitterYs[i], w, mJitterYs[i], mPaint); + } + mPaint.setStrokeWidth(dp(2)); + mPaint.setColor(Color.WHITE); + canvas.drawPoints(mJitterPoints, mPaint); + mPaint.setColor(0xFF2196F3); + canvas.drawPoints(mJitterAvgPoints, mPaint); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + int graphWidth = (int) ((w - mLabelWidth - dp(1)) / mDensity); + float[] oldJitterPoints = mJitterPoints; + float[] oldJitterAvgPoints = mJitterAvgPoints; + mJitterPoints = new float[graphWidth * 2]; + mJitterAvgPoints = new float[graphWidth * 2]; + for (int i = 0; i < mJitterPoints.length; i += 2) { + mJitterPoints[i] = mLabelWidth + (i / 2 + 1) * mDensity; + mJitterAvgPoints[i] = mJitterPoints[i]; + } + if (oldJitterPoints != null) { + int newIndexShift = Math.max(mJitterPoints.length - oldJitterPoints.length, 0); + int oldIndexShift = oldJitterPoints.length - mJitterPoints.length; + for (int i = 1 + newIndexShift; i < mJitterPoints.length; i += 2) { + mJitterPoints[i] = oldJitterPoints[i + oldIndexShift]; + mJitterAvgPoints[i] = oldJitterAvgPoints[i + oldIndexShift]; + } + } + mGraphMaxMs = JITTER_LINES_MS[JITTER_LINES_MS.length - 1] + .5f; + mGraphScale = (h / mGraphMaxMs); + for (int i = 0; i < JITTER_LINES_MS.length; i++) { + mJitterYs[i] = (float) Math.floor(h - mGraphScale * JITTER_LINES_MS[i]); + } + } + + private static String[] makeLabels(float[] divisions) { + String[] ret = new String[divisions.length]; + for (int i = 0; i < divisions.length; i++) { + ret[i] = Float.toString(divisions[i]); + } + return ret; + } + } + + private final OnFrameMetricsAvailableListener mMetricsListener = new OnFrameMetricsAvailableListener() { + private final static double WEIGHT = 40; + private long mPreviousFrameTotal; + private double mJitterMma; + private double mUiFrametimeMma; + private double mRtFrametimeMma; + private double mTotalFrametimeMma; + private double mMostlyTotalFrametimeMma; + private boolean mNeedsFirstValues = true; + + @Override + public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics, + int dropCountSinceLastInvocation) { + if (frameMetrics.getMetric(FrameMetrics.FIRST_DRAW_FRAME) == 1) { + return; + } + + long uiDuration = frameMetrics.getMetric(FrameMetrics.INPUT_HANDLING_DURATION) + + frameMetrics.getMetric(FrameMetrics.ANIMATION_DURATION) + + frameMetrics.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION) + + frameMetrics.getMetric(FrameMetrics.DRAW_DURATION); + long rtDuration = frameMetrics.getMetric(FrameMetrics.SYNC_DURATION) + + frameMetrics.getMetric(FrameMetrics.COMMAND_ISSUE_DURATION); + long totalDuration = frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION); + long jitter = Math.abs(totalDuration - mPreviousFrameTotal); + if (mNeedsFirstValues) { + mJitterMma = 0; + mUiFrametimeMma = uiDuration; + mRtFrametimeMma = rtDuration; + mTotalFrametimeMma = totalDuration; + mMostlyTotalFrametimeMma = uiDuration + rtDuration; + mNeedsFirstValues = false; + } else { + mJitterMma = add(mJitterMma, jitter); + mUiFrametimeMma = add(mUiFrametimeMma, uiDuration); + mRtFrametimeMma = add(mRtFrametimeMma, rtDuration); + mTotalFrametimeMma = add(mTotalFrametimeMma, totalDuration); + mMostlyTotalFrametimeMma = add(mMostlyTotalFrametimeMma, uiDuration + rtDuration); + } + mPreviousFrameTotal = totalDuration; + mUpdateHandler.obtainMessage(R.id.jitter_mma, + String.format("Jitter: %.3fms", toMs(mJitterMma))).sendToTarget(); + mUpdateHandler.obtainMessage(R.id.totalish_mma, + String.format("CPU-total duration: %.3fms", toMs(mMostlyTotalFrametimeMma))).sendToTarget(); + mUpdateHandler.obtainMessage(R.id.ui_frametime_mma, + String.format("UI duration: %.3fms", toMs(mUiFrametimeMma))).sendToTarget(); + mUpdateHandler.obtainMessage(R.id.rt_frametime_mma, + String.format("RT duration: %.3fms", toMs(mRtFrametimeMma))).sendToTarget(); + mUpdateHandler.obtainMessage(R.id.total_mma, + String.format("Total duration: %.3fms", toMs(mTotalFrametimeMma))).sendToTarget(); + mUpdateHandler.obtainMessage(R.id.graph, (int) (jitter / 1000), + (int) (mJitterMma / 1000)).sendToTarget(); + } + + double add(double previous, double today) { + return (((WEIGHT - 1) * previous) + today) / WEIGHT; + } + + double toMs(double val) { + return val / 1000000; + } + }; + + private static final class AnimatedBackgroundDrawable extends Drawable { + private static final int FROM_COLOR = 0xFF18FFFF; + private static final int TO_COLOR = 0xFF40C4FF; + private static final int DURATION = 1400; + + private final Paint mPaint; + private boolean mReverse; + private long mStartTime; + private int mColor; + + private boolean mReverseX; + private boolean mReverseY; + private float mX; + private float mY; + private float mRadius; + private float mMoveStep = 10.0f; + + public AnimatedBackgroundDrawable() { + mPaint = new Paint(); + mPaint.setColor(0xFFFFFF00); + mPaint.setAntiAlias(true); + } + + @Override + public void draw(Canvas canvas) { + stepColor(); + canvas.drawColor(mColor); + + mX += (mReverseX ? -mMoveStep : mMoveStep); + mY += (mReverseY ? -mMoveStep : mMoveStep); + clampXY(); + canvas.drawCircle(mX, mY, mRadius, mPaint); + + invalidateSelf(); + } + + private void clampXY() { + if (mX <= mRadius) { + mReverseX = false; + mX = mRadius; + } + if (mY <= mRadius) { + mReverseY = false; + mY = mRadius; + } + float maxX = getBounds().width() - mRadius; + if (mX >= maxX) { + mReverseX = true; + mX = maxX; + } + float maxY = getBounds().height() - mRadius; + if (mY >= maxY) { + mReverseY = true; + mY = maxY; + } + } + + @Override + protected void onBoundsChange(Rect bounds) { + super.onBoundsChange(bounds); + mMoveStep = Math.min(bounds.width(), bounds.height()) / 130.0f; + mRadius = Math.min(bounds.width(), bounds.height()) / 20.0f; + } + + @Override + public void setAlpha(int alpha) { + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + } + + @Override + public int getOpacity() { + return PixelFormat.OPAQUE; + } + + private void stepColor() { + if (mStartTime == 0) { + mStartTime = AnimationUtils.currentAnimationTimeMillis(); + } + float frac = (AnimationUtils.currentAnimationTimeMillis() - mStartTime) + / (float) DURATION; + if (frac > 1.0f) frac = 1.0f; + int dest = mReverse ? FROM_COLOR : TO_COLOR; + int src = mReverse ? TO_COLOR : FROM_COLOR; + int r = (int) (Color.red(src) + (Color.red(dest) - Color.red(src)) * frac); + int g = (int) (Color.green(src) + (Color.green(dest) - Color.green(src)) * frac); + int b = (int) (Color.blue(src) + (Color.blue(dest) - Color.blue(src)) * frac); + mColor = Color.rgb(r, g, b); + if (frac == 1.0f) { + mStartTime = 0; + mReverse = !mReverse; + } + } + } +} diff --git a/src/org/evolution/pixelparts/utils/AutoHBMUtils.java b/src/org/evolution/pixelparts/utils/AutoHBMUtils.java new file mode 100644 index 0000000..d8fb00f --- /dev/null +++ b/src/org/evolution/pixelparts/utils/AutoHBMUtils.java @@ -0,0 +1,33 @@ +package org.evolution.pixelparts.utils; + +import android.content.Context; +import android.content.Intent; +import android.os.UserHandle; + +import org.evolution.pixelparts.services.AutoHBMService; +import org.evolution.pixelparts.PixelParts; + +public class AutoHBMUtils { + + private static boolean mAutoHBMEnabled = false; + + private static void startAutoHBM(Context context) { + context.startServiceAsUser(new Intent(context, AutoHBMService.class), + UserHandle.CURRENT); + mAutoHBMEnabled = true; + } + + private static void stopAutoHBM(Context context) { + mAutoHBMEnabled = false; + context.stopServiceAsUser(new Intent(context, AutoHBMService.class), + UserHandle.CURRENT); + } + + public static void enableAutoHBM(Context context) { + if (PixelParts.isAutoHBMEnabled(context) && !mAutoHBMEnabled) { + startAutoHBM(context); + } else if (!PixelParts.isAutoHBMEnabled(context) && mAutoHBMEnabled) { + stopAutoHBM(context); + } + } +} diff --git a/src/org/evolution/pixelparts/utils/ShakeUtils.kt b/src/org/evolution/pixelparts/utils/ShakeUtils.kt new file mode 100644 index 0000000..cab78f8 --- /dev/null +++ b/src/org/evolution/pixelparts/utils/ShakeUtils.kt @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2021-2022 Miku UI + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.evolution.pixelparts.utils; + +import android.content.Context +import android.hardware.Sensor +import android.hardware.SensorEvent +import android.hardware.SensorEventListener +import android.hardware.SensorManager +import kotlin.math.sqrt + +class ShakeUtils(context: Context) : SensorEventListener { + private var mOnShakeListeners: ArrayList? = null + + // Last time we triggered shake + private var mLastShakeTime = 0L + private var mLastUpdateTime = 0L + + // Last position we triggered shake + private var mLastX = 0f + private var mLastY = 0f + private var mLastZ = 0f + + interface OnShakeListener { + fun onShake(speed: Double) + } + + fun bindShakeListener(listener: OnShakeListener?) { + if (listener != null) { + mOnShakeListeners?.add(listener) + } + } + + fun unBindShakeListener(listener: OnShakeListener) { + mOnShakeListeners?.remove(listener) + } + + override fun onSensorChanged(event: SensorEvent) { + if (event == null) { + return + } + val curUpdateTime = System.currentTimeMillis() + // Times between two shakes + val timeInterval = curUpdateTime - mLastUpdateTime + if (timeInterval < SHAKE_INTERVAL_MILLISECOND) { + return + } + if (event.values.size < 3) { + return + } + mLastUpdateTime = curUpdateTime + val x = event.values[0] + val y = event.values[1] + val z = event.values[2] + val deltaX = x - mLastX + val deltaY = y - mLastY + val deltaZ = z - mLastZ + mLastX = x + mLastY = y + mLastZ = z + val speed = + sqrt((deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ).toDouble()) * 1000.0 / timeInterval + if (speed >= SPEED_SHAKE_MILLISECONDS) { + startShake(speed) + } + } + + private fun startShake(speed: Double) { + val curShakeTime = System.currentTimeMillis() + if (curShakeTime - mLastShakeTime < MIN_SHAKE_INTERVAL) { + return + } + mLastShakeTime = curShakeTime + mOnShakeListeners?.let { + for (i in it.indices) { + it[i].onShake(speed) + } + } + } + + override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {} + + companion object { + + // Minimal time interval of position changes + private const val MIN_SHAKE_INTERVAL = 1024 + + // Minimal shake speed + private const val SPEED_SHAKE_MILLISECONDS = 400 + + // Minimal time interval between two shakes + private const val SHAKE_INTERVAL_MILLISECOND = 55 + } + + init { + val mSensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager + val sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) + mSensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_UI) + mOnShakeListeners = ArrayList() + } +} diff --git a/src/org/evolution/pixelparts/utils/Utils.java b/src/org/evolution/pixelparts/utils/Utils.java new file mode 100644 index 0000000..4fa5e83 --- /dev/null +++ b/src/org/evolution/pixelparts/utils/Utils.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2016 The CyanogenMod Project + * 2018-2022 crDroid Android Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.evolution.pixelparts.utils; + +import android.content.Context; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.BufferedReader; +import java.io.FileReader; + +import org.evolution.pixelparts.R; + +import android.util.Log; + +public class Utils { + + private static final String TAG = Utils.class.getSimpleName(); + + /** + * Reads the first line of text from the given file. + * Reference {@link BufferedReader#readLine()} for clarification on what a line is + * + * @return the read line contents, or null on failure + */ + public static String readOneLine(String fileName) { + String line = null; + BufferedReader reader = null; + + try { + reader = new BufferedReader(new FileReader(fileName), 512); + line = reader.readLine(); + } catch (FileNotFoundException e) { + Log.w(TAG, "No such file " + fileName + " for reading", e); + } catch (IOException e) { + Log.e(TAG, "Could not read from file " + fileName, e); + } finally { + try { + if (reader != null) { + reader.close(); + } + } catch (IOException e) { + // Ignored, not much we can do anyway + } + } + + return line; + } + + /** + * Write a string value to the specified file. + * @param filename The filename + * @param value The value + */ + public static void writeValue(String filename, String value) { + if (filename == null) { + return; + } + try { + FileOutputStream fos = new FileOutputStream(new File(filename)); + fos.write(value.getBytes()); + fos.flush(); + fos.close(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Returns the contents of the file with the given filename, or the specified default value if the file cannot be read. + * + * @param filename the name of the file to read + * @param defValue the default value to return if the file cannot be read + * @return the contents of the file as a String, or the default value if the file cannot be read + */ + public static String getFileValue(String filename, String defValue) { + String fileValue = readOneLine(filename); + if (fileValue != null) { + return fileValue; + } + return defValue; + } + + /** + * Checks whether the given file exists + * + * @return true if exists, false if not + */ + public static boolean fileExists(String fileName) { + final File file = new File(fileName); + return file.exists(); + } + + /** + * Checks whether the given file is readable + * + * @return true if readable, false if not + */ + public static boolean isFileReadable(String fileName) { + final File file = new File(fileName); + return file.exists() && file.canRead(); + } + + /** + * Checks whether the given file is writable + * + * @return true if writable, false if not + */ + public static boolean isFileWritable(String fileName) { + final File file = new File(fileName); + return file.exists() && file.canWrite(); + } +}