diff --git a/DeviceSettings/Android.bp b/DeviceSettings/Android.bp index 123c01c..005c551 100644 --- a/DeviceSettings/Android.bp +++ b/DeviceSettings/Android.bp @@ -19,7 +19,18 @@ android_app { "org.lineageos.platform.internal", ], + required: [ + "privapp-permissions-device_settings.xml", + ], + optimize: { proguard_flags_files: ["proguard.flags"], }, } + +prebuilt_etc { + name: "privapp-permissions-device_settings.xml", + src: "privapp-permissions-device_settings.xml", + sub_dir: "permissions", + system_ext_specific: true, +} diff --git a/DeviceSettings/AndroidManifest.xml b/DeviceSettings/AndroidManifest.xml index b2bb563..2976355 100644 --- a/DeviceSettings/AndroidManifest.xml +++ b/DeviceSettings/AndroidManifest.xml @@ -26,6 +26,9 @@ + + + @@ -35,13 +38,23 @@ android:label="@string/device_title" android:defaultToDeviceProtectedStorage="true" android:directBootAware="true" - android:theme="@style/Theme.SubSettingsBase.Expressive" + android:theme="@style/Theme.DeviceSettings" android:exported="true"> + + + + + + + + @@ -88,5 +101,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DeviceSettings/privapp-permissions-device_settings.xml b/DeviceSettings/privapp-permissions-device_settings.xml new file mode 100644 index 0000000..3147def --- /dev/null +++ b/DeviceSettings/privapp-permissions-device_settings.xml @@ -0,0 +1,21 @@ + + + + + + + + diff --git a/DeviceSettings/res/drawable/banner_fade_gradient.xml b/DeviceSettings/res/drawable/banner_fade_gradient.xml new file mode 100644 index 0000000..4567557 --- /dev/null +++ b/DeviceSettings/res/drawable/banner_fade_gradient.xml @@ -0,0 +1,6 @@ + + + diff --git a/DeviceSettings/res/drawable/ic_bypass_charging.xml b/DeviceSettings/res/drawable/ic_bypass_charging.xml new file mode 100644 index 0000000..68749f9 --- /dev/null +++ b/DeviceSettings/res/drawable/ic_bypass_charging.xml @@ -0,0 +1,27 @@ + + + + + diff --git a/DeviceSettings/res/drawable/ic_game_bar.xml b/DeviceSettings/res/drawable/ic_game_bar.xml new file mode 100644 index 0000000..54f24ae --- /dev/null +++ b/DeviceSettings/res/drawable/ic_game_bar.xml @@ -0,0 +1,11 @@ + + + + diff --git a/DeviceSettings/res/drawable/oplus_banner.png b/DeviceSettings/res/drawable/oplus_banner.png new file mode 100644 index 0000000..b84bbac Binary files /dev/null and b/DeviceSettings/res/drawable/oplus_banner.png differ diff --git a/DeviceSettings/res/layout/activity_bypass_charging.xml b/DeviceSettings/res/layout/activity_bypass_charging.xml new file mode 100644 index 0000000..9ee55f5 --- /dev/null +++ b/DeviceSettings/res/layout/activity_bypass_charging.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/DeviceSettings/res/layout/activity_game_bar.xml b/DeviceSettings/res/layout/activity_game_bar.xml new file mode 100644 index 0000000..c4964ba --- /dev/null +++ b/DeviceSettings/res/layout/activity_game_bar.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/DeviceSettings/res/layout/activity_game_bar_app_selector.xml b/DeviceSettings/res/layout/activity_game_bar_app_selector.xml new file mode 100644 index 0000000..eec564c --- /dev/null +++ b/DeviceSettings/res/layout/activity_game_bar_app_selector.xml @@ -0,0 +1,5 @@ + + diff --git a/DeviceSettings/res/layout/banner_collapsing_toolbar.xml b/DeviceSettings/res/layout/banner_collapsing_toolbar.xml new file mode 100644 index 0000000..b97f8e8 --- /dev/null +++ b/DeviceSettings/res/layout/banner_collapsing_toolbar.xml @@ -0,0 +1,22 @@ + + + + + + + + + diff --git a/DeviceSettings/res/layout/device_settings_activity.xml b/DeviceSettings/res/layout/device_settings_activity.xml new file mode 100644 index 0000000..0ef57c0 --- /dev/null +++ b/DeviceSettings/res/layout/device_settings_activity.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + diff --git a/DeviceSettings/res/layout/game_bar.xml b/DeviceSettings/res/layout/game_bar.xml new file mode 100644 index 0000000..e8948d9 --- /dev/null +++ b/DeviceSettings/res/layout/game_bar.xml @@ -0,0 +1,10 @@ + + + diff --git a/DeviceSettings/res/layout/game_bar_app_selector.xml b/DeviceSettings/res/layout/game_bar_app_selector.xml new file mode 100644 index 0000000..6ce63b5 --- /dev/null +++ b/DeviceSettings/res/layout/game_bar_app_selector.xml @@ -0,0 +1,6 @@ + + diff --git a/DeviceSettings/res/layout/game_bar_app_selector_item.xml b/DeviceSettings/res/layout/game_bar_app_selector_item.xml new file mode 100644 index 0000000..38c45e5 --- /dev/null +++ b/DeviceSettings/res/layout/game_bar_app_selector_item.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + diff --git a/DeviceSettings/res/values-ru-rRU/strings.xml b/DeviceSettings/res/values-ru-rRU/strings.xml index 7005fad..79e361a 100644 --- a/DeviceSettings/res/values-ru-rRU/strings.xml +++ b/DeviceSettings/res/values-ru-rRU/strings.xml @@ -16,8 +16,8 @@ --> - Настройки OnePlus - Изменение особых настроек устройства OnePlus + Aston Settings + Extra settings for OnePlus Ace3 | 12R Переключатель режима @@ -69,4 +69,26 @@ Снять ограничения касаний Улучшение в играх нескольких касаний Может увеличить вероятность случайного прикосновения + + + Gaming + Extra settings for gamers + + + Bypass charging + Supply power directly to the phone + Bypass charging supplies power directly to the board, instead of the battery, helping to reduce device heat, when running demanding applications like games. + Bypass charging is not supported on this device + Enabled + Disabled + Coudn\'t enable Bypass Charging. + + + GameBar + Enable the system overlay (FPS, Temp, etc.) + Overlay permission is required + Overlay permission granted + Overlay permission denied + GameBar + Toggle the game overlay diff --git a/DeviceSettings/res/values/arrays.xml b/DeviceSettings/res/values/arrays.xml index 1ce60e9..bb5a5dc 100644 --- a/DeviceSettings/res/values/arrays.xml +++ b/DeviceSettings/res/values/arrays.xml @@ -111,4 +111,104 @@ 64 63 + + + + New API (Default) + Legacy Sysfs + + + new + legacy + + + + + Every 500ms + Every second + Every 2 seconds + Every 5 seconds + + + 500 + 1000 + 2000 + 5000 + + + + + Top Left + Top Center + Top Right + Bottom Left + Bottom Center + Bottom Right + Custom Draggable + + + top_left + top_center + top_right + bottom_left + bottom_center + bottom_right + draggable + + + + + White + Crimson + Fruit Salad + Royal Blue + Amber + Teal + Electric Violet + Magenta + + + #FFFFFF + #DC143C + #4CAF50 + #4169E1 + #FFBF00 + #008080 + #8A2BE2 + #FF1493 + + + + + Full + Minimal + + + full + minimal + + + + + Side-by-Side + Stacked + + + side_by_side + stacked + + + + + 1 second + 3 seconds + 5 seconds + 10 seconds + + + 1000 + 3000 + 5000 + 10000 + diff --git a/DeviceSettings/res/values/strings.xml b/DeviceSettings/res/values/strings.xml index dc98e72..b643744 100644 --- a/DeviceSettings/res/values/strings.xml +++ b/DeviceSettings/res/values/strings.xml @@ -16,8 +16,8 @@ --> - OnePlus Settings - Adjust OnePlus specific device settings + Aston Settings + Extra settings for OnePlus Ace3 | 12R Alert slider @@ -69,4 +69,26 @@ Unlimit edge touch Improve your multi-touch experience in games May increase the possibility of accidental touch + + + Gaming + Extra settings for gamers + + + Bypass charging + Supply power directly to the phone + Bypass charging supplies power directly to the board, instead of the battery, helping to reduce device heat, when running demanding applications like games. + Bypass charging is not supported on this device + Enabled + Disabled + Coudn\'t enable Bypass Charging. + + + GameBar + Enable the system overlay (FPS, Temp, etc.) + Overlay permission is required + Overlay permission granted + Overlay permission denied + GameBar + Toggle the game overlay diff --git a/DeviceSettings/res/values/themes.xml b/DeviceSettings/res/values/themes.xml new file mode 100644 index 0000000..dd06f5d --- /dev/null +++ b/DeviceSettings/res/values/themes.xml @@ -0,0 +1,25 @@ + + + + + + + diff --git a/DeviceSettings/res/xml/bypass_charging_settings.xml b/DeviceSettings/res/xml/bypass_charging_settings.xml new file mode 100644 index 0000000..1be278f --- /dev/null +++ b/DeviceSettings/res/xml/bypass_charging_settings.xml @@ -0,0 +1,14 @@ + + + + + + diff --git a/DeviceSettings/res/xml/game_bar_preferences.xml b/DeviceSettings/res/xml/game_bar_preferences.xml new file mode 100644 index 0000000..6fea1af --- /dev/null +++ b/DeviceSettings/res/xml/game_bar_preferences.xml @@ -0,0 +1,252 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DeviceSettings/res/xml/main.xml b/DeviceSettings/res/xml/main.xml index 14dddeb..422cb86 100644 --- a/DeviceSettings/res/xml/main.xml +++ b/DeviceSettings/res/xml/main.xml @@ -93,4 +93,22 @@ android:title="@string/usb2_fc_title" /> --> + + + + + + diff --git a/DeviceSettings/src/org/lineageos/device/DeviceSettings/BootCompletedReceiver.java b/DeviceSettings/src/org/lineageos/device/DeviceSettings/BootCompletedReceiver.java new file mode 100644 index 0000000..71787be --- /dev/null +++ b/DeviceSettings/src/org/lineageos/device/DeviceSettings/BootCompletedReceiver.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2015 The CyanogenMod Project + * 2017-2020 The LineageOS Project + * + * 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 org.lineageos.device.DeviceSettings; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import androidx.preference.PreferenceManager; + +import org.lineageos.device.DeviceSettings.gamebar.GameBar; +import org.lineageos.device.DeviceSettings.gamebar.GameBarMonitorService; + +public class BootCompletedReceiver extends BroadcastReceiver { + + @Override + public void onReceive(final Context context, Intent intent) { + String action = intent.getAction(); + if (Intent.ACTION_BOOT_COMPLETED.equals(action)) { + restoreGameBarOverlayState(context); + } + } + + private void restoreGameBarOverlayState(Context context) { + var prefs = PreferenceManager.getDefaultSharedPreferences(context); + boolean mainEnabled = prefs.getBoolean("game_bar_enable", false); + boolean autoEnabled = prefs.getBoolean("game_bar_auto_enable", false); + if (mainEnabled) { + // Restore game bar preferences + GameBar.getInstance(context).applyPreferences(); + GameBar.getInstance(context).show(); + } + if (autoEnabled) { + // Start GameBarMonitorService + Intent monitorIntent = new Intent(context, GameBarMonitorService.class); + context.startService(monitorIntent); + } + } +} diff --git a/DeviceSettings/src/org/lineageos/device/DeviceSettings/DeviceSettings.java b/DeviceSettings/src/org/lineageos/device/DeviceSettings/DeviceSettings.java index cb598bf..73e0052 100644 --- a/DeviceSettings/src/org/lineageos/device/DeviceSettings/DeviceSettings.java +++ b/DeviceSettings/src/org/lineageos/device/DeviceSettings/DeviceSettings.java @@ -384,14 +384,14 @@ public class DeviceSettings extends SettingsBasePreferenceFragment }); } - public static void restoreFastChargeSetting(Context context) { - if (Utils.fileWritable(FILE_FAST_CHARGE)) { - SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context); - boolean value = sharedPrefs.getBoolean(KEY_USB2_SWITCH, - Utils.getFileValueAsBoolean(FILE_FAST_CHARGE, false)); - Utils.writeValue(FILE_FAST_CHARGE, value ? "1" : "0"); - } - } + // public static void restoreFastChargeSetting(Context context) { + // if (Utils.fileWritable(FILE_FAST_CHARGE)) { + // SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context); + // boolean value = sharedPrefs.getBoolean(KEY_USB2_SWITCH, + // Utils.getFileValueAsBoolean(FILE_FAST_CHARGE, false)); + // Utils.writeValue(FILE_FAST_CHARGE, value ? "1" : "0"); + // } + // } private static int getDefaultResIdForUsage(String usage) { switch (usage) { diff --git a/DeviceSettings/src/org/lineageos/device/DeviceSettings/DeviceSettingsActivity.java b/DeviceSettings/src/org/lineageos/device/DeviceSettings/DeviceSettingsActivity.java index 8967fe9..ae09c83 100644 --- a/DeviceSettings/src/org/lineageos/device/DeviceSettings/DeviceSettingsActivity.java +++ b/DeviceSettings/src/org/lineageos/device/DeviceSettings/DeviceSettingsActivity.java @@ -16,29 +16,51 @@ package org.lineageos.device.DeviceSettings; -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.os.Bundle; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import androidx.preference.PreferenceFragment; -import androidx.preference.PreferenceManager; +import android.view.View; import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity; +import com.google.android.material.appbar.AppBarLayout; +import com.google.android.material.appbar.CollapsingToolbarLayout; + public class DeviceSettingsActivity extends CollapsingToolbarBaseActivity { + private View bannerFadeOverlay; + private boolean pinned = false; // Set true to pin the fade overlay @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + setTitle(" "); + // Load your fragment as usual getSupportFragmentManager().beginTransaction().replace( - com.android.settingslib.collapsingtoolbar.R.id.content_frame, - new DeviceSettings()).commit(); + R.id.content_frame, + new DeviceSettings()).commit(); + + // Inject banner dynamically into CollapsingToolbarLayout + CollapsingToolbarLayout collapsingToolbar = + findViewById(R.id.collapsing_toolbar); + if (collapsingToolbar != null) { + View banner = getLayoutInflater().inflate(R.layout.banner_collapsing_toolbar, collapsingToolbar, false); + + // You may want to insert at position 0 to ensure it's on top + collapsingToolbar.addView(banner, 0); + + bannerFadeOverlay = banner.findViewById(R.id.bannerFadeOverlay); + + // Animate fade overlay on scroll + AppBarLayout appBar = findViewById(R.id.app_bar); + if (appBar != null) { + appBar.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> { + if (bannerFadeOverlay == null) return; + int totalScrollRange = appBarLayout.getTotalScrollRange(); + float offsetFraction = Math.abs(verticalOffset) / (float) totalScrollRange; + float maxAlpha = 0.8f; + float alpha = pinned ? maxAlpha : maxAlpha * (1 - offsetFraction); + bannerFadeOverlay.setAlpha(alpha); + }); + } + } } } diff --git a/DeviceSettings/src/org/lineageos/device/DeviceSettings/TileHandlerActivity.java b/DeviceSettings/src/org/lineageos/device/DeviceSettings/TileHandlerActivity.java new file mode 100644 index 0000000..c3769cd --- /dev/null +++ b/DeviceSettings/src/org/lineageos/device/DeviceSettings/TileHandlerActivity.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2025 kenway215 + * + * 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 org.lineageos.device.DeviceSettings; + +import android.app.Activity; +import android.content.ComponentName; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.provider.Settings; +import android.service.quicksettings.TileService; +import android.util.Log; + +import java.util.HashMap; +import java.util.Map; + +import org.lineageos.device.DeviceSettings.bypasschrg.BypassChargingActivity; +import org.lineageos.device.DeviceSettings.bypasschrg.BypassChargingTile; +import org.lineageos.device.DeviceSettings.gamebar.GameBarSettingsActivity; +import org.lineageos.device.DeviceSettings.gamebar.GameBarTileService; + +public final class TileHandlerActivity extends Activity { + private static final String TAG = "TileHandlerActivity"; + + // Map QS Tile services to their corresponding activity + private static final Map> TILE_ACTIVITY_MAP = new HashMap<>(); + + static { + TILE_ACTIVITY_MAP.put(GameBarTileService.class.getName(), GameBarSettingsActivity.class); + TILE_ACTIVITY_MAP.put(BypassChargingTile.class.getName(), BypassChargingActivity.class); + } + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + final Intent intent = getIntent(); + if (intent == null || !TileService.ACTION_QS_TILE_PREFERENCES.equals(intent.getAction())) { + Log.e(TAG, "Invalid or null intent received"); + finish(); + return; + } + + final ComponentName qsTile = intent.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME); + if (qsTile == null) { + Log.e(TAG, "No QS tile component found in intent"); + finish(); + return; + } + + final String qsName = qsTile.getClassName(); + final Intent targetIntent = new Intent(); + + // Check if the tile is mapped to an activity + if (TILE_ACTIVITY_MAP.containsKey(qsName)) { + targetIntent.setClass(this, TILE_ACTIVITY_MAP.get(qsName)); + Log.d(TAG, "Launching settings activity for QS tile: " + qsName); + } else { + // Default: Open app settings for the QS tile's package + final String packageName = qsTile.getPackageName(); + if (packageName == null) { + Log.e(TAG, "QS tile package name is null"); + finish(); + return; + } + targetIntent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + targetIntent.setData(Uri.fromParts("package", packageName, null)); + Log.d(TAG, "Opening app info for package: " + packageName); + } + + // Ensure proper navigation behavior + targetIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | + Intent.FLAG_ACTIVITY_CLEAR_TASK | + Intent.FLAG_ACTIVITY_NEW_TASK); + + startActivity(targetIntent); + finish(); + } +} diff --git a/DeviceSettings/src/org/lineageos/device/DeviceSettings/bypasschrg/BypassChargingActivity.java b/DeviceSettings/src/org/lineageos/device/DeviceSettings/bypasschrg/BypassChargingActivity.java new file mode 100644 index 0000000..8b9eeca --- /dev/null +++ b/DeviceSettings/src/org/lineageos/device/DeviceSettings/bypasschrg/BypassChargingActivity.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2025 The LineageOS Project + * + * 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 org.lineageos.device.DeviceSettings.bypasschrg; + +import android.os.Bundle; + +import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity; + +import org.lineageos.device.DeviceSettings.R; + +public class BypassChargingActivity extends CollapsingToolbarBaseActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_bypass_charging); + setTitle(getString(R.string.bypass_charging_title)); + } +} diff --git a/DeviceSettings/src/org/lineageos/device/DeviceSettings/bypasschrg/BypassChargingController.java b/DeviceSettings/src/org/lineageos/device/DeviceSettings/bypasschrg/BypassChargingController.java new file mode 100644 index 0000000..af62ab6 --- /dev/null +++ b/DeviceSettings/src/org/lineageos/device/DeviceSettings/bypasschrg/BypassChargingController.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2025 The LineageOS Project + * Copyright (C) 2025 AlphaDroid + * + * 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 org.lineageos.device.DeviceSettings.bypasschrg; + +import static lineageos.health.HealthInterface.MODE_AUTO; +import static lineageos.health.HealthInterface.MODE_LIMIT; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.SharedPreferences; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.provider.Settings; +import android.util.Log; +import android.widget.Toast; +import androidx.preference.PreferenceManager; + +import org.lineageos.device.DeviceSettings.R; +import org.lineageos.device.DeviceSettings.utils.FileUtils; + +/** + * This class is implemented to coexist with Lineage Charging Control (CC). + * Bypass Charging will override (disable) CC, while it's enabled. + * CC status will be restored, when Bypass Charging is disabled. + * Any user changes to CC settings, while Bypass Charging is enabled, + * will override Bypass Charging settings. + */ +public class BypassChargingController { + + private static final boolean DEBUG = false; + + private static final String TAG = "BypassChargingController"; + private static final String BYPASS_CHARGING_NODE = "/sys/class/oplus_chg/battery/mmi_charging_enable"; + private static final String KEY_BYPASS_CHARGING_ENABLED = "bypass_charging_enabled"; + + // Bypass modes + private static final String BYPASS_CHARGING_ENABLED = "0"; + private static final String BYPASS_CHARGING_DISABLED = "1"; + + private static final int CC_LIMIT_MIN = 10; + private static final int CC_LIMIT_MAX = 100; + private static final int CC_LIMIT_DEF = 80; + + // Charging Control settings + private static final String KEY_CHARGING_CONTROL_ENABLED = "charging_control_enabled"; + private static final String KEY_CHARGING_CONTROL_MODE = "charging_control_mode"; + private static final String KEY_CHARGING_CONTROL_LIMIT = "charging_control_charging_limit"; + + private Context mContext; + private ContentResolver mContentResolver; + + private static BypassChargingController sInstance; + public static synchronized BypassChargingController getInstance(Context context) { + if (sInstance == null) { + sInstance = new BypassChargingController(context); + } + return sInstance; + } + + private BypassChargingController(Context context) { + mContext = context.getApplicationContext(); + mContentResolver = mContext.getContentResolver(); + } + + private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) { + @Override + public void onChange(boolean selfChange, Uri uri) { + switch(uri.getLastPathSegment()) { + case KEY_CHARGING_CONTROL_ENABLED: + case KEY_CHARGING_CONTROL_MODE: + case KEY_CHARGING_CONTROL_LIMIT: + break; + } + } + }; + + public boolean isBypassChargingSupported() { + return isNodeAccessible(BYPASS_CHARGING_NODE); + } + + public boolean isBypassChargingEnabled() { + try { + String value = FileUtils.readOneLine(BYPASS_CHARGING_NODE); + return value != null && BYPASS_CHARGING_ENABLED.equals(value); + } catch (Exception e) { + Log.e(TAG, "Failed to read bypass sysnode", e); + return false; + } + } + + private boolean isNodeAccessible(String node) { + try { + String value = FileUtils.readOneLine(node); + return true; + } catch (Exception e) { + Log.e(TAG, "Node " + node + " not accessible", e); + return false; + } + } + + private boolean writeToNode(String value) { + try { + FileUtils.writeLine(BYPASS_CHARGING_NODE, value); + } catch (Exception e) { + Log.e(TAG, "Failed to write bypass sysnode", e); + return false; + } + return true; + } + + public void setBypassCharging(boolean enable) { + if (enable) { + enableBypassCharging(); + } + else { + disableBypassCharging(); + } + } + + private void enableBypassCharging() { + setChargingControlEnabled(true); + setChargingControlMode(MODE_LIMIT); + setChargingControlLimit(CC_LIMIT_MIN); + writeToNode(BYPASS_CHARGING_ENABLED); + } + + public void disableBypassCharging() { + writeToNode(BYPASS_CHARGING_DISABLED); + setChargingControlLimit(CC_LIMIT_DEF); + // setChargingControlMode(MODE_AUTO); + setChargingControlEnabled(false); + } + + private void saveBypassChargingEnabled(boolean enabled) { + PreferenceManager.getDefaultSharedPreferences(mContext) + .edit() + .putBoolean(KEY_BYPASS_CHARGING_ENABLED, enabled) + .commit(); + } + + private boolean isSavedBypassChargingEnabled() { + return PreferenceManager.getDefaultSharedPreferences(mContext) + .getBoolean(KEY_BYPASS_CHARGING_ENABLED, false); + } + + private void backupChargingControlSettings() { + PreferenceManager.getDefaultSharedPreferences(mContext) + .edit() + .putInt(KEY_CHARGING_CONTROL_MODE, getChargingControlMode()) + .putInt(KEY_CHARGING_CONTROL_LIMIT, getChargingControlLimit()) + .putBoolean(KEY_CHARGING_CONTROL_ENABLED, isChargingControlEnabled()) + .commit(); + } + + private void restoreChargingControlSettings() { + SharedPreferences sharedPreferences = + PreferenceManager.getDefaultSharedPreferences(mContext); + setChargingControlMode(sharedPreferences.getInt( + KEY_CHARGING_CONTROL_LIMIT, CC_LIMIT_DEF)); + setChargingControlMode(sharedPreferences.getInt( + KEY_CHARGING_CONTROL_MODE, MODE_AUTO)); + setChargingControlEnabled(sharedPreferences.getBoolean( + KEY_CHARGING_CONTROL_ENABLED, false)); + } + + private boolean isChargingControlEnabled() { + return Settings.System.getInt(mContentResolver, + KEY_CHARGING_CONTROL_ENABLED, 0) != 0; + } + + private void setChargingControlEnabled(boolean enabled) { + Settings.System.putInt(mContentResolver, + KEY_CHARGING_CONTROL_ENABLED, enabled ? 1 : 0); + } + + private int getChargingControlMode() { + return Settings.System.getInt(mContentResolver, + KEY_CHARGING_CONTROL_MODE, MODE_AUTO); + } + + private void setChargingControlMode(int mode) { + Settings.System.putInt(mContentResolver, + KEY_CHARGING_CONTROL_MODE, mode); + } + + private int getChargingControlLimit() { + return Settings.System.getInt(mContentResolver, + KEY_CHARGING_CONTROL_LIMIT, CC_LIMIT_DEF); + } + + private void setChargingControlLimit(int limit) { + if (limit < CC_LIMIT_MIN || limit > CC_LIMIT_MAX) { + return; + } + Settings.System.putInt(mContentResolver, + KEY_CHARGING_CONTROL_LIMIT, limit); + } + + private void showToast(int resId) { + Toast.makeText(mContext, mContext.getString(resId), + Toast.LENGTH_LONG).show(); + } +} diff --git a/DeviceSettings/src/org/lineageos/device/DeviceSettings/bypasschrg/BypassChargingFragment.java b/DeviceSettings/src/org/lineageos/device/DeviceSettings/bypasschrg/BypassChargingFragment.java new file mode 100644 index 0000000..4c5f460 --- /dev/null +++ b/DeviceSettings/src/org/lineageos/device/DeviceSettings/bypasschrg/BypassChargingFragment.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2025 The LineageOS Project + * + * 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 org.lineageos.device.DeviceSettings.bypasschrg; + +import android.os.Bundle; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.TwoStatePreference; + +import org.lineageos.device.DeviceSettings.R; + +public class BypassChargingFragment extends PreferenceFragmentCompat { + + private static final String KEY_BYPASS_CHARGING = "bypass_charging"; + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + setPreferencesFromResource(R.xml.bypass_charging_settings, rootKey); + + BypassChargingController bypassController = + BypassChargingController.getInstance(getContext()); + boolean bypassSupported = bypassController.isBypassChargingSupported(); + + TwoStatePreference bypassPreference = findPreference(KEY_BYPASS_CHARGING); + bypassPreference.setEnabled(bypassSupported); + if (bypassSupported) { + bypassPreference.setChecked(bypassController.isBypassChargingEnabled()); + bypassPreference.setOnPreferenceChangeListener((pref, newValue) -> { + bypassController.setBypassCharging((boolean) newValue); + return true; + }); + } else { + bypassPreference.setSummary(R.string.bypass_charging_unavailable); + } + } +} diff --git a/DeviceSettings/src/org/lineageos/device/DeviceSettings/bypasschrg/BypassChargingTile.java b/DeviceSettings/src/org/lineageos/device/DeviceSettings/bypasschrg/BypassChargingTile.java new file mode 100644 index 0000000..6dce7b7 --- /dev/null +++ b/DeviceSettings/src/org/lineageos/device/DeviceSettings/bypasschrg/BypassChargingTile.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2025 kenway214 + * + * 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 org.lineageos.device.DeviceSettings.bypasschrg; + +import android.service.quicksettings.Tile; +import android.service.quicksettings.TileService; + +import org.lineageos.device.DeviceSettings.R; + +public class BypassChargingTile extends TileService { + + + private BypassChargingController mBypassController; + private boolean mEnabled; + + @Override + public void onCreate() { + super.onCreate(); + mBypassController = BypassChargingController.getInstance(this); + } + + @Override + public void onStartListening() { + mEnabled = mBypassController.isBypassChargingEnabled(); + updateTileState(); + } + + @Override + public void onClick() { + if (mEnabled == mBypassController.isBypassChargingEnabled()) { + mEnabled = !mEnabled; + updateTileState(); + mBypassController.setBypassCharging(mEnabled); + } + } + + private void updateTileState() { + Tile tile = getQsTile(); + if (tile == null) return; + + tile.setState(mEnabled ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE); + tile.setLabel(getString(R.string.bypass_charging_title)); + tile.setContentDescription(getString(R.string.bypass_charging_summary)); + tile.updateTile(); + } +} diff --git a/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/ForegroundAppDetector.java b/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/ForegroundAppDetector.java new file mode 100644 index 0000000..23d890d --- /dev/null +++ b/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/ForegroundAppDetector.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2025 kenway214 + * + * 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 org.lineageos.device.DeviceSettings.gamebar; + +import android.app.ActivityManager; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Build; +import android.util.Log; + +import java.lang.reflect.Method; +import java.util.List; + +public class ForegroundAppDetector { + + private static final String TAG = "ForegroundAppDetector"; + + public static String getForegroundPackageName(Context context) { + + String pkg = tryGetRunningTasks(context); + if (pkg != null) { + return pkg; + } + pkg = tryReflectActivityTaskManager(); + if (pkg != null) { + return pkg; + } + return "Unknown"; + } + + private static String tryGetRunningTasks(Context context) { + try { + if (context.checkSelfPermission("android.permission.GET_TASKS") + == PackageManager.PERMISSION_GRANTED) { + + ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + List tasks = am.getRunningTasks(1); + if (tasks != null && !tasks.isEmpty()) { + ActivityManager.RunningTaskInfo top = tasks.get(0); + if (top.topActivity != null) { + return top.topActivity.getPackageName(); + } + } + } else { + Log.w(TAG, "GET_TASKS permission not granted to this system app?"); + } + } catch (Exception e) { + Log.e(TAG, "tryGetRunningTasks error: ", e); + } + return null; + } + + private static String tryReflectActivityTaskManager() { + try { + Class atmClass = Class.forName("android.app.ActivityTaskManager"); + Method getServiceMethod = atmClass.getDeclaredMethod("getService"); + getServiceMethod.setAccessible(true); + Object atmService = getServiceMethod.invoke(null); + Method getTasksMethod = atmService.getClass().getMethod("getTasks", int.class); + @SuppressWarnings("unchecked") + List taskList = (List) getTasksMethod.invoke(atmService, 1); + if (taskList != null && !taskList.isEmpty()) { + + Object firstTask = taskList.get(0); + + Class rtiClass = firstTask.getClass(); + Method getTopActivityMethod = rtiClass.getDeclaredMethod("getTopActivity"); + Object compName = getTopActivityMethod.invoke(firstTask); + if (compName != null) { + + Method getPackageNameMethod = compName.getClass().getMethod("getPackageName"); + String pkgName = (String) getPackageNameMethod.invoke(compName); + return pkgName; + } + } + } catch (Exception e) { + Log.e(TAG, "tryReflectActivityTaskManager error: ", e); + } + return null; + } +} diff --git a/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBar.java b/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBar.java new file mode 100644 index 0000000..0a4aa94 --- /dev/null +++ b/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBar.java @@ -0,0 +1,774 @@ +/* + * Copyright (C) 2025 kenway214 + * + * 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 org.lineageos.device.DeviceSettings.gamebar; + +import android.app.usage.UsageStatsManager; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.graphics.drawable.GradientDrawable; +import android.os.Handler; +import android.os.Looper; +import android.provider.Settings; +import android.util.TypedValue; +import android.view.GestureDetector; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.view.WindowManager; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.preference.PreferenceManager; + +import org.lineageos.device.DeviceSettings.R; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +public class GameBar { + + private static GameBar sInstance; + public static synchronized GameBar getInstance(Context context) { + if (sInstance == null) { + sInstance = new GameBar(context.getApplicationContext()); + } + return sInstance; + } + + private static final String FPS_PATH = "/sys/class/drm/sde-crtc-0/measured_fps"; + private static final String BATTERY_TEMP_PATH = "/sys/class/power_supply/battery/temp"; + + private static final String PREF_KEY_X = "game_bar_x"; + private static final String PREF_KEY_Y = "game_bar_y"; + + private final Context mContext; + private final WindowManager mWindowManager; + private final Handler mHandler; + + private View mOverlayView; + private LinearLayout mRootLayout; + private WindowManager.LayoutParams mLayoutParams; + private boolean mIsShowing = false; + + private int mTextSizeSp = 16; + private int mBackgroundAlpha = 128; + private int mCornerRadius = 16; + private int mPaddingDp = 12; + private String mTitleColorHex = "#FFFFFF"; + private String mValueColorHex = "#FFFFFF"; + private String mOverlayFormat = "full"; + private String mPosition = "top_left"; + private String mSplitMode = "stacked"; + private int mUpdateIntervalMs = 1000; + private boolean mDraggable = false; + + private boolean mShowBatteryTemp = false; + private boolean mShowCpuUsage = false; + private boolean mShowCpuClock = false; + private boolean mShowCpuTemp = false; + private boolean mShowRam = false; + private boolean mShowFps = false; + + private boolean mShowGpuUsage = false; + private boolean mShowGpuClock = false; + private boolean mShowGpuTemp = false; + + private boolean mLongPressEnabled = false; + private long mLongPressThresholdMs = 1000; + private boolean mPressActive = false; + private float mDownX, mDownY; + private static final float TOUCH_SLOP = 20f; + + private GestureDetector mGestureDetector; + private boolean mDoubleTapCaptureEnabled = false; + private boolean mSingleTapToggleEnabled = false; + private GradientDrawable mBgDrawable; + + private int mItemSpacingDp = 8; + + private final Runnable mLongPressRunnable = new Runnable() { + @Override + public void run() { + if (mPressActive) { + openOverlaySettings(); + mPressActive = false; + } + } + }; + + private final Runnable mUpdateRunnable = new Runnable() { + @Override + public void run() { + if (mIsShowing) { + updateStats(); + mHandler.postDelayed(this, mUpdateIntervalMs); + } + } + }; + + private GameBar(Context context) { + mContext = context; + mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + mHandler = new Handler(Looper.getMainLooper()); + + mBgDrawable = new GradientDrawable(); + applyBackgroundStyle(); + + mGestureDetector = new GestureDetector(mContext, new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onDoubleTap(MotionEvent e) { + if (mDoubleTapCaptureEnabled) { + if (GameDataExport.getInstance().isCapturing()) { + GameDataExport.getInstance().stopCapture(); + Toast.makeText(mContext, "Capture Stopped", Toast.LENGTH_SHORT).show(); + } else { + GameDataExport.getInstance().startCapture(); + Toast.makeText(mContext, "Capture Started", Toast.LENGTH_SHORT).show(); + } + return true; + } + return super.onDoubleTap(e); + } + + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + if (mSingleTapToggleEnabled) { + mOverlayFormat = "full".equals(mOverlayFormat) ? "minimal" : "full"; + PreferenceManager.getDefaultSharedPreferences(mContext) + .edit() + .putString("game_bar_format", mOverlayFormat) + .apply(); + Toast.makeText(mContext, "Overlay Format: " + mOverlayFormat, Toast.LENGTH_SHORT).show(); + updateStats(); + return true; + } + return super.onSingleTapConfirmed(e); + } + }); + } + + public void applyPreferences() { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext); + + mShowFps = prefs.getBoolean("game_bar_fps_enable", false); + mShowBatteryTemp = prefs.getBoolean("game_bar_temp_enable", false); + mShowCpuUsage = prefs.getBoolean("game_bar_cpu_usage_enable", false); + mShowCpuClock = prefs.getBoolean("game_bar_cpu_clock_enable", false); + mShowCpuTemp = prefs.getBoolean("game_bar_cpu_temp_enable", false); + mShowRam = prefs.getBoolean("game_bar_ram_enable", false); + + mShowGpuUsage = prefs.getBoolean("game_bar_gpu_usage_enable", false); + mShowGpuClock = prefs.getBoolean("game_bar_gpu_clock_enable", false); + mShowGpuTemp = prefs.getBoolean("game_bar_gpu_temp_enable", false); + + mDoubleTapCaptureEnabled = prefs.getBoolean("game_bar_doubletap_capture", false); + mSingleTapToggleEnabled = prefs.getBoolean("game_bar_single_tap_toggle", false); + + updateSplitMode(prefs.getString("game_bar_split_mode", "stacked")); + updateTextSize(prefs.getInt("game_bar_text_size", 16)); + updateBackgroundAlpha(prefs.getInt("game_bar_background_alpha", 128)); + updateCornerRadius(prefs.getInt("game_bar_corner_radius", 16)); + updatePadding(prefs.getInt("game_bar_padding", 12)); + updateTitleColor(prefs.getString("game_bar_title_color", "#FFFFFF")); + updateValueColor(prefs.getString("game_bar_value_color", "#4CAF50")); + updateOverlayFormat(prefs.getString("game_bar_format", "full")); + updateUpdateInterval(prefs.getString("game_bar_update_interval", "1000")); + updatePosition(prefs.getString("game_bar_position", "top_left")); + + int spacing = prefs.getInt("game_bar_item_spacing", 8); + updateItemSpacing(spacing); + + mLongPressEnabled = prefs.getBoolean("game_bar_longpress_enable", false); + String lpTimeoutStr = prefs.getString("game_bar_longpress_timeout", "1000"); + try { + long lpt = Long.parseLong(lpTimeoutStr); + setLongPressThresholdMs(lpt); + } catch (NumberFormatException ignored) {} + } + + public void show() { + if (mIsShowing) return; + + applyPreferences(); + + mLayoutParams = new WindowManager.LayoutParams( + WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + PixelFormat.TRANSLUCENT + ); + + if ("draggable".equals(mPosition)) { + mDraggable = true; + loadSavedPosition(mLayoutParams); + if (mLayoutParams.x == 0 && mLayoutParams.y == 0) { + mLayoutParams.gravity = Gravity.TOP | Gravity.START; + mLayoutParams.x = 0; + mLayoutParams.y = 100; + } + } else { + mDraggable = false; + applyPosition(mLayoutParams, mPosition); + } + + mOverlayView = new LinearLayout(mContext); + mOverlayView.setLayoutParams(new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT + )); + mRootLayout = (LinearLayout) mOverlayView; + applySplitMode(); + applyBackgroundStyle(); + applyPadding(); + + mOverlayView.setOnTouchListener((v, event) -> { + if (mGestureDetector != null && mGestureDetector.onTouchEvent(event)) { + return true; + } + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + if (mDraggable) { + initialX = mLayoutParams.x; + initialY = mLayoutParams.y; + initialTouchX = event.getRawX(); + initialTouchY = event.getRawY(); + } + if (mLongPressEnabled) { + mPressActive = true; + mDownX = event.getRawX(); + mDownY = event.getRawY(); + mHandler.postDelayed(mLongPressRunnable, mLongPressThresholdMs); + } + return true; + case MotionEvent.ACTION_MOVE: + if (mLongPressEnabled && mPressActive) { + float dx = Math.abs(event.getRawX() - mDownX); + float dy = Math.abs(event.getRawY() - mDownY); + if (dx > TOUCH_SLOP || dy > TOUCH_SLOP) { + mPressActive = false; + mHandler.removeCallbacks(mLongPressRunnable); + } + } + if (mDraggable) { + int deltaX = (int) (event.getRawX() - initialTouchX); + int deltaY = (int) (event.getRawY() - initialTouchY); + mLayoutParams.x = initialX + deltaX; + mLayoutParams.y = initialY + deltaY; + mWindowManager.updateViewLayout(mOverlayView, mLayoutParams); + } + return true; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + if (mLongPressEnabled && mPressActive) { + mPressActive = false; + mHandler.removeCallbacks(mLongPressRunnable); + } + if (mDraggable) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext); + prefs.edit() + .putInt(PREF_KEY_X, mLayoutParams.x) + .putInt(PREF_KEY_Y, mLayoutParams.y) + .apply(); + } + return true; + } + return false; + }); + + mWindowManager.addView(mOverlayView, mLayoutParams); + mIsShowing = true; + startUpdates(); + + // Start the FPS meter if using the new API method. + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) { + GameBarFpsMeter.getInstance(mContext).start(); + } + } + + private int initialX, initialY; + private float initialTouchX, initialTouchY; + + public void hide() { + if (!mIsShowing) return; + mHandler.removeCallbacksAndMessages(null); + if (mOverlayView != null) { + mWindowManager.removeView(mOverlayView); + mOverlayView = null; + } + mIsShowing = false; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) { + GameBarFpsMeter.getInstance(mContext).stop(); + } + } + + private void updateStats() { + if (!mIsShowing || mRootLayout == null) return; + + mRootLayout.removeAllViews(); + + List statViews = new ArrayList<>(); + + // 1) FPS + float fpsVal = GameBarFpsMeter.getInstance(mContext).getFps(); + String fpsStr = fpsVal >= 0 ? String.format(Locale.getDefault(), "%.0f", fpsVal) : "N/A"; + if (mShowFps) { + statViews.add(createStatLine("FPS", fpsStr)); + } + + // 2) Battery temp + String batteryTempStr = "N/A"; + if (mShowBatteryTemp) { + String tmp = readLine(BATTERY_TEMP_PATH); + if (tmp != null && !tmp.isEmpty()) { + try { + int raw = Integer.parseInt(tmp.trim()); + float c = raw / 10f; + batteryTempStr = String.format(Locale.getDefault(), "%.1f", c); + } catch (NumberFormatException ignored) {} + } + statViews.add(createStatLine("Temp", batteryTempStr + "°C")); + } + + // 3) CPU usage + String cpuUsageStr = "N/A"; + if (mShowCpuUsage) { + cpuUsageStr = GameBarCpuInfo.getCpuUsage(); + String display = "N/A".equals(cpuUsageStr) ? "N/A" : cpuUsageStr + "%"; + statViews.add(createStatLine("CPU", display)); + } + + // 4) CPU freq + if (mShowCpuClock) { + List freqs = GameBarCpuInfo.getCpuFrequencies(); + if (!freqs.isEmpty()) { + statViews.add(buildCpuFreqView(freqs)); + } + } + + // 5) CPU temp + String cpuTempStr = "N/A"; + if (mShowCpuTemp) { + cpuTempStr = GameBarCpuInfo.getCpuTemp(); + statViews.add(createStatLine("CPU Temp", "N/A".equals(cpuTempStr) ? "N/A" : cpuTempStr + "°C")); + } + + // 6) RAM usage + String ramStr = "N/A"; + if (mShowRam) { + ramStr = GameBarMemInfo.getRamUsage(); + statViews.add(createStatLine("RAM", "N/A".equals(ramStr) ? "N/A" : ramStr + " MB")); + } + + // 7) GPU usage + String gpuUsageStr = "N/A"; + if (mShowGpuUsage) { + gpuUsageStr = GameBarGpuInfo.getGpuUsage(); + statViews.add(createStatLine("GPU", "N/A".equals(gpuUsageStr) ? "N/A" : gpuUsageStr + "%")); + } + + // 8) GPU clock + String gpuClockStr = "N/A"; + if (mShowGpuClock) { + gpuClockStr = GameBarGpuInfo.getGpuClock(); + statViews.add(createStatLine("GPU Freq", "N/A".equals(gpuClockStr) ? "N/A" : gpuClockStr + "MHz")); + } + + // 9) GPU temp + String gpuTempStr = "N/A"; + if (mShowGpuTemp) { + gpuTempStr = GameBarGpuInfo.getGpuTemp(); + statViews.add(createStatLine("GPU Temp", "N/A".equals(gpuTempStr) ? "N/A" : gpuTempStr + "°C")); + } + + if ("side_by_side".equals(mSplitMode)) { + mRootLayout.setOrientation(LinearLayout.HORIZONTAL); + if ("minimal".equals(mOverlayFormat)) { + for (int i = 0; i < statViews.size(); i++) { + mRootLayout.addView(statViews.get(i)); + if (i < statViews.size() - 1) { + mRootLayout.addView(createDotView()); + } + } + } else { + for (View view : statViews) { + mRootLayout.addView(view); + } + } + } else { + mRootLayout.setOrientation(LinearLayout.VERTICAL); + for (View view : statViews) { + mRootLayout.addView(view); + } + } + + if (GameDataExport.getInstance().isCapturing()) { + String dateTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(new Date()); + String pkgName = ForegroundAppDetector.getForegroundPackageName(mContext); + + GameDataExport.getInstance().addOverlayData( + dateTime, + pkgName, + fpsStr, + batteryTempStr, + cpuUsageStr, + cpuTempStr, + gpuUsageStr, + gpuClockStr, + gpuTempStr + ); + } + + if (mLayoutParams != null) { + mWindowManager.updateViewLayout(mOverlayView, mLayoutParams); + } + } + + private View buildCpuFreqView(List freqs) { + LinearLayout freqContainer = new LinearLayout(mContext); + freqContainer.setOrientation(LinearLayout.HORIZONTAL); + + int spacingPx = dpToPx(mContext, mItemSpacingDp); + LinearLayout.LayoutParams outerLp = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ); + outerLp.setMargins(spacingPx, spacingPx / 2, spacingPx, spacingPx / 2); + freqContainer.setLayoutParams(outerLp); + + if ("full".equals(mOverlayFormat)) { + TextView labelTv = new TextView(mContext); + labelTv.setTextSize(TypedValue.COMPLEX_UNIT_SP, mTextSizeSp); + try { + labelTv.setTextColor(Color.parseColor(mTitleColorHex)); + } catch (Exception e) { + labelTv.setTextColor(Color.WHITE); + } + labelTv.setText("CPU Freq "); + freqContainer.addView(labelTv); + } + + LinearLayout verticalFreqs = new LinearLayout(mContext); + verticalFreqs.setOrientation(LinearLayout.VERTICAL); + + for (String freqLine : freqs) { + LinearLayout lineLayout = new LinearLayout(mContext); + lineLayout.setOrientation(LinearLayout.HORIZONTAL); + + TextView freqTv = new TextView(mContext); + freqTv.setTextSize(TypedValue.COMPLEX_UNIT_SP, mTextSizeSp); + try { + freqTv.setTextColor(Color.parseColor(mValueColorHex)); + } catch (Exception e) { + freqTv.setTextColor(Color.WHITE); + } + freqTv.setText(freqLine); + + lineLayout.addView(freqTv); + + LinearLayout.LayoutParams lineLp = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ); + lineLp.setMargins(spacingPx, spacingPx / 4, spacingPx, spacingPx / 4); + lineLayout.setLayoutParams(lineLp); + + verticalFreqs.addView(lineLayout); + } + + freqContainer.addView(verticalFreqs); + return freqContainer; + } + + private LinearLayout createStatLine(String title, String rawValue) { + LinearLayout lineLayout = new LinearLayout(mContext); + lineLayout.setOrientation(LinearLayout.HORIZONTAL); + + if ("full".equals(mOverlayFormat)) { + TextView tvTitle = new TextView(mContext); + tvTitle.setTextSize(TypedValue.COMPLEX_UNIT_SP, mTextSizeSp); + try { + tvTitle.setTextColor(Color.parseColor(mTitleColorHex)); + } catch (Exception e) { + tvTitle.setTextColor(Color.WHITE); + } + tvTitle.setText(title.isEmpty() ? "" : title + " "); + + TextView tvValue = new TextView(mContext); + tvValue.setTextSize(TypedValue.COMPLEX_UNIT_SP, mTextSizeSp); + try { + tvValue.setTextColor(Color.parseColor(mValueColorHex)); + } catch (Exception e) { + tvValue.setTextColor(Color.WHITE); + } + tvValue.setText(rawValue); + + lineLayout.addView(tvTitle); + lineLayout.addView(tvValue); + } else { + TextView tvMinimal = new TextView(mContext); + tvMinimal.setTextSize(TypedValue.COMPLEX_UNIT_SP, mTextSizeSp); + try { + tvMinimal.setTextColor(Color.parseColor(mValueColorHex)); + } catch (Exception e) { + tvMinimal.setTextColor(Color.WHITE); + } + tvMinimal.setText(rawValue); + lineLayout.addView(tvMinimal); + } + + int spacingPx = dpToPx(mContext, mItemSpacingDp); + LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ); + lp.setMargins(spacingPx, spacingPx / 2, spacingPx, spacingPx / 2); + lineLayout.setLayoutParams(lp); + + return lineLayout; + } + + private View createDotView() { + TextView dotView = new TextView(mContext); + dotView.setTextSize(TypedValue.COMPLEX_UNIT_SP, mTextSizeSp); + try { + dotView.setTextColor(Color.parseColor(mValueColorHex)); + } catch (Exception e) { + dotView.setTextColor(Color.WHITE); + } + dotView.setText(" . "); + return dotView; + } + + public void setShowBatteryTemp(boolean show) { mShowBatteryTemp = show; } + public void setShowCpuUsage(boolean show) { mShowCpuUsage = show; } + public void setShowCpuClock(boolean show) { mShowCpuClock = show; } + public void setShowCpuTemp(boolean show) { mShowCpuTemp = show; } + public void setShowRam(boolean show) { mShowRam = show; } + public void setShowFps(boolean show) { mShowFps = show; } + + public void setShowGpuUsage(boolean show) { mShowGpuUsage = show; } + public void setShowGpuClock(boolean show) { mShowGpuClock = show; } + public void setShowGpuTemp(boolean show) { mShowGpuTemp = show; } + + public void updateTextSize(int sp) { + mTextSizeSp = sp; + } + + public void updateCornerRadius(int radius) { + mCornerRadius = radius; + applyBackgroundStyle(); + } + + public void updateBackgroundAlpha(int alpha) { + mBackgroundAlpha = alpha; + applyBackgroundStyle(); + } + + public void updatePadding(int dp) { + mPaddingDp = dp; + applyPadding(); + } + + public void updateTitleColor(String hex) { + mTitleColorHex = hex; + } + + public void updateValueColor(String hex) { + mValueColorHex = hex; + } + + public void updateOverlayFormat(String format) { + mOverlayFormat = format; + if (mIsShowing) { + updateStats(); + } + } + + public void updateItemSpacing(int dp) { + mItemSpacingDp = dp; + if (mIsShowing) { + updateStats(); + } + } + + private void applyBackgroundStyle() { + int color = Color.argb(mBackgroundAlpha, 0, 0, 0); + mBgDrawable.setColor(color); + mBgDrawable.setCornerRadius(mCornerRadius); + + if (mOverlayView != null) { + mOverlayView.setBackground(mBgDrawable); + } + } + + private void applyPadding() { + if (mRootLayout != null) { + int px = dpToPx(mContext, mPaddingDp); + mRootLayout.setPadding(px, px, px, px); + } + } + + public void updatePosition(String pos) { + mPosition = pos; + if (mIsShowing && mOverlayView != null && mLayoutParams != null) { + if ("draggable".equals(mPosition)) { + mDraggable = true; + loadSavedPosition(mLayoutParams); + if (mLayoutParams.x == 0 && mLayoutParams.y == 0) { + mLayoutParams.gravity = Gravity.TOP | Gravity.START; + mLayoutParams.x = 0; + mLayoutParams.y = 100; + } + } else { + mDraggable = false; + applyPosition(mLayoutParams, mPosition); + } + mWindowManager.updateViewLayout(mOverlayView, mLayoutParams); + } + } + + public void updateSplitMode(String mode) { + mSplitMode = mode; + if (mIsShowing && mOverlayView != null) { + applySplitMode(); + updateStats(); + } + } + + public void updateUpdateInterval(String intervalStr) { + try { + mUpdateIntervalMs = Integer.parseInt(intervalStr); + } catch (NumberFormatException e) { + mUpdateIntervalMs = 1000; + } + if (mIsShowing) { + startUpdates(); + } + } + + public void setLongPressEnabled(boolean enabled) { + mLongPressEnabled = enabled; + } + public void setLongPressThresholdMs(long ms) { + mLongPressThresholdMs = ms; + } + + public void setDoubleTapCaptureEnabled(boolean enabled) { + mDoubleTapCaptureEnabled = enabled; + } + + public void setSingleTapToggleEnabled(boolean enabled) { + mSingleTapToggleEnabled = enabled; + } + + private void startUpdates() { + mHandler.removeCallbacksAndMessages(null); + mHandler.post(mUpdateRunnable); + } + + private void applySplitMode() { + if (mRootLayout == null) return; + if ("side_by_side".equals(mSplitMode)) { + mRootLayout.setOrientation(LinearLayout.HORIZONTAL); + } else { + mRootLayout.setOrientation(LinearLayout.VERTICAL); + } + } + + private void loadSavedPosition(WindowManager.LayoutParams lp) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext); + int savedX = prefs.getInt(PREF_KEY_X, Integer.MIN_VALUE); + int savedY = prefs.getInt(PREF_KEY_Y, Integer.MIN_VALUE); + if (savedX != Integer.MIN_VALUE && savedY != Integer.MIN_VALUE) { + lp.gravity = Gravity.TOP | Gravity.START; + lp.x = savedX; + lp.y = savedY; + } + } + + private void applyPosition(WindowManager.LayoutParams lp, String pos) { + switch (pos) { + case "top_left": + lp.gravity = Gravity.TOP | Gravity.START; + lp.x = 0; + lp.y = 100; + break; + case "top_center": + lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL; + lp.y = 100; + break; + case "top_right": + lp.gravity = Gravity.TOP | Gravity.END; + lp.x = 0; + lp.y = 100; + break; + case "bottom_left": + lp.gravity = Gravity.BOTTOM | Gravity.START; + lp.x = 0; + lp.y = 100; + break; + case "bottom_center": + lp.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL; + lp.y = 100; + break; + case "bottom_right": + lp.gravity = Gravity.BOTTOM | Gravity.END; + lp.x = 0; + lp.y = 100; + break; + default: + lp.gravity = Gravity.TOP | Gravity.START; + lp.x = 0; + lp.y = 100; + break; + } + } + + private String readLine(String path) { + try (BufferedReader br = new BufferedReader(new FileReader(path))) { + return br.readLine(); + } catch (IOException e) { + return null; + } + } + + private void openOverlaySettings() { + try { + Intent intent = new Intent(mContext, GameBarSettingsActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivity(intent); + } catch (Exception e) { + // Exception ignored + } + } + + private static int dpToPx(Context context, int dp) { + float scale = context.getResources().getDisplayMetrics().density; + return Math.round(dp * scale); + } +} diff --git a/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarAppRemoverActivity.java b/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarAppRemoverActivity.java new file mode 100644 index 0000000..1ee1409 --- /dev/null +++ b/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarAppRemoverActivity.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2025 kenway214 + * + * 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 org.lineageos.device.DeviceSettings.gamebar; + +import android.os.Bundle; +import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity; +import org.lineageos.device.DeviceSettings.R; + +public class GameBarAppRemoverActivity extends CollapsingToolbarBaseActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_game_bar_app_selector); + setTitle("Remove Auto-Enable Apps"); + + if (savedInstanceState == null) { + getSupportFragmentManager().beginTransaction() + .replace(R.id.content_frame, new GameBarAppRemoverFragment()) + .commit(); + } + } +} diff --git a/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarAppRemoverFragment.java b/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarAppRemoverFragment.java new file mode 100644 index 0000000..f794b60 --- /dev/null +++ b/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarAppRemoverFragment.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2025 kenway214 + * + * 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 org.lineageos.device.DeviceSettings.gamebar; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.preference.PreferenceManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Toast; +import org.lineageos.device.DeviceSettings.R; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class GameBarAppRemoverFragment extends Fragment { + + private RecyclerView recyclerView; + private GameBarAutoAppsAdapter adapter; + private PackageManager packageManager; + private List autoAppsList; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.game_bar_app_selector, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + recyclerView = view.findViewById(R.id.app_list); + packageManager = getContext().getPackageManager(); + recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + loadAutoApps(); + } + + private void loadAutoApps() { + Set autoAppsSet = getSavedAutoApps(); + autoAppsList = new ArrayList<>(); + for (String pkg : autoAppsSet) { + try { + ApplicationInfo info = packageManager.getApplicationInfo(pkg, 0); + autoAppsList.add(info); + } catch (PackageManager.NameNotFoundException e) { + } + } + adapter = new GameBarAutoAppsAdapter(packageManager, autoAppsList, new GameBarAutoAppsAdapter.OnAppRemoveListener() { + @Override + public void onAppRemove(ApplicationInfo appInfo) { + removeAppFromAutoList(appInfo.packageName); + Toast.makeText(getContext(), appInfo.loadLabel(packageManager) + " removed.", Toast.LENGTH_SHORT).show(); + autoAppsList.remove(appInfo); + adapter.notifyDataSetChanged(); + } + }); + recyclerView.setAdapter(adapter); + } + + private Set getSavedAutoApps() { + return PreferenceManager.getDefaultSharedPreferences(getContext()) + .getStringSet(GameBarAppSelectorFragment.PREF_AUTO_APPS, new HashSet<>()); + } + + private void removeAppFromAutoList(String packageName) { + Set autoApps = new HashSet<>(getSavedAutoApps()); + autoApps.remove(packageName); + PreferenceManager.getDefaultSharedPreferences(getContext()) + .edit().putStringSet(GameBarAppSelectorFragment.PREF_AUTO_APPS, autoApps).apply(); + } +} diff --git a/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarAppSelectorActivity.java b/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarAppSelectorActivity.java new file mode 100644 index 0000000..f486be7 --- /dev/null +++ b/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarAppSelectorActivity.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2025 kenway214 + * + * 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 org.lineageos.device.DeviceSettings.gamebar; + +import android.os.Bundle; +import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity; +import org.lineageos.device.DeviceSettings.R; + +public class GameBarAppSelectorActivity extends CollapsingToolbarBaseActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_game_bar_app_selector); + setTitle("Select Apps for GameBar"); + + if (savedInstanceState == null) { + getSupportFragmentManager().beginTransaction() + .replace(R.id.content_frame, new GameBarAppSelectorFragment()) + .commit(); + } + } +} diff --git a/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarAppSelectorFragment.java b/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarAppSelectorFragment.java new file mode 100644 index 0000000..e390190 --- /dev/null +++ b/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarAppSelectorFragment.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2025 kenway214 + * + * 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 org.lineageos.device.DeviceSettings.gamebar; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.preference.PreferenceManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Toast; +import org.lineageos.device.DeviceSettings.R; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class GameBarAppSelectorFragment extends Fragment { + + public static final String PREF_AUTO_APPS = "game_bar_auto_apps"; + + private RecyclerView recyclerView; + private GameBarAppsAdapter adapter; + private PackageManager packageManager; + private List allApps; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.game_bar_app_selector, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + recyclerView = view.findViewById(R.id.app_list); + packageManager = getContext().getPackageManager(); + recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + loadApps(); + } + + private void loadApps() { + allApps = new ArrayList<>(); + List installedApps = packageManager.getInstalledApplications(PackageManager.GET_META_DATA); + Set autoApps = getSavedAutoApps(); + for (ApplicationInfo appInfo : installedApps) { + if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0 && + !appInfo.packageName.equals(getContext().getPackageName()) && + !autoApps.contains(appInfo.packageName)) { + allApps.add(appInfo); + } + } + adapter = new GameBarAppsAdapter(packageManager, allApps, new GameBarAppsAdapter.OnAppClickListener() { + @Override + public void onAppClick(ApplicationInfo appInfo) { + addAppToAutoList(appInfo.packageName); + Toast.makeText(getContext(), appInfo.loadLabel(packageManager) + " added.", Toast.LENGTH_SHORT).show(); + allApps.remove(appInfo); + adapter.notifyDataSetChanged(); + } + }); + recyclerView.setAdapter(adapter); + } + + private Set getSavedAutoApps() { + return PreferenceManager.getDefaultSharedPreferences(getContext()) + .getStringSet(PREF_AUTO_APPS, new HashSet<>()); + } + + private void addAppToAutoList(String packageName) { + Set autoApps = new HashSet<>(getSavedAutoApps()); + autoApps.add(packageName); + PreferenceManager.getDefaultSharedPreferences(getContext()) + .edit().putStringSet(PREF_AUTO_APPS, autoApps).apply(); + } +} diff --git a/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarAppsAdapter.java b/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarAppsAdapter.java new file mode 100644 index 0000000..405aba2 --- /dev/null +++ b/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarAppsAdapter.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2025 kenway214 + * + * 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 org.lineageos.device.DeviceSettings.gamebar; + +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import org.lineageos.device.DeviceSettings.R; +import java.util.List; + +public class GameBarAppsAdapter extends RecyclerView.Adapter { + + public interface OnAppClickListener { + void onAppClick(ApplicationInfo appInfo); + } + + private PackageManager packageManager; + private List apps; + private OnAppClickListener listener; + + public GameBarAppsAdapter(PackageManager packageManager, List apps, OnAppClickListener listener) { + this.packageManager = packageManager; + this.apps = apps; + this.listener = listener; + } + + @NonNull + @Override + public GameBarAppsAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.game_bar_app_selector_item, parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull GameBarAppsAdapter.ViewHolder holder, int position) { + final ApplicationInfo appInfo = apps.get(position); + holder.appName.setText(appInfo.loadLabel(packageManager)); + holder.appPackage.setText(appInfo.packageName); + holder.appIcon.setImageDrawable(appInfo.loadIcon(packageManager)); + holder.itemView.setOnClickListener(v -> { + if (listener != null) { + listener.onAppClick(appInfo); + } + }); + } + + @Override + public int getItemCount() { + return apps.size(); + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + TextView appName; + TextView appPackage; + ImageView appIcon; + + public ViewHolder(@NonNull View itemView) { + super(itemView); + appName = itemView.findViewById(R.id.app_name); + appPackage = itemView.findViewById(R.id.app_package); + appIcon = itemView.findViewById(R.id.app_icon); + } + } +} diff --git a/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarAutoAppsAdapter.java b/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarAutoAppsAdapter.java new file mode 100644 index 0000000..c197c2a --- /dev/null +++ b/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarAutoAppsAdapter.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2025 kenway214 + * + * 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 org.lineageos.device.DeviceSettings.gamebar; + +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import org.lineageos.device.DeviceSettings.R; +import java.util.List; + +public class GameBarAutoAppsAdapter extends RecyclerView.Adapter { + + public interface OnAppRemoveListener { + void onAppRemove(ApplicationInfo appInfo); + } + + private PackageManager packageManager; + private List apps; + private OnAppRemoveListener listener; + + public GameBarAutoAppsAdapter(PackageManager packageManager, List apps, OnAppRemoveListener listener) { + this.packageManager = packageManager; + this.apps = apps; + this.listener = listener; + } + + @NonNull + @Override + public GameBarAutoAppsAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.game_bar_app_selector_item, parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull GameBarAutoAppsAdapter.ViewHolder holder, int position) { + final ApplicationInfo appInfo = apps.get(position); + holder.appName.setText(appInfo.loadLabel(packageManager)); + holder.appPackage.setText(appInfo.packageName); + holder.appIcon.setImageDrawable(appInfo.loadIcon(packageManager)); + holder.itemView.setOnClickListener(v -> { + if (listener != null) { + listener.onAppRemove(appInfo); + } + }); + } + + @Override + public int getItemCount() { + return apps.size(); + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + TextView appName; + TextView appPackage; + ImageView appIcon; + + public ViewHolder(@NonNull View itemView) { + super(itemView); + appName = itemView.findViewById(R.id.app_name); + appPackage = itemView.findViewById(R.id.app_package); + appIcon = itemView.findViewById(R.id.app_icon); + } + } +} diff --git a/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarBootReceiver.java b/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarBootReceiver.java new file mode 100644 index 0000000..8131ef3 --- /dev/null +++ b/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarBootReceiver.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2025 kenway214 + * + * 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 org.lineageos.device.DeviceSettings.gamebar; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import androidx.preference.PreferenceManager; + +public class GameBarBootReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (Intent.ACTION_BOOT_COMPLETED.equals(action) + || Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(action)) { + restoreOverlayState(context); + } + } + + private void restoreOverlayState(Context context) { + var prefs = PreferenceManager.getDefaultSharedPreferences(context); + boolean mainEnabled = prefs.getBoolean("game_bar_enable", false); + boolean autoEnabled = prefs.getBoolean("game_bar_auto_enable", false); + if (mainEnabled) { + GameBar.getInstance(context).applyPreferences(); + GameBar.getInstance(context).show(); + } + if (autoEnabled) { + Intent monitorIntent = new Intent(context, GameBarMonitorService.class); + context.startService(monitorIntent); + } + } +} diff --git a/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarCpuInfo.java b/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarCpuInfo.java new file mode 100644 index 0000000..3f4000f --- /dev/null +++ b/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarCpuInfo.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2025 kenway214 + * + * 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 org.lineageos.device.DeviceSettings.gamebar; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public class GameBarCpuInfo { + + private static long sPrevIdle = -1; + private static long sPrevTotal = -1; + + private static final String CPU_TEMP_PATH = "/sys/class/thermal/thermal_zone0/temp"; + + public static String getCpuUsage() { + String line = readLine("/proc/stat"); + if (line == null || !line.startsWith("cpu ")) return "N/A"; + String[] parts = line.split("\\s+"); + if (parts.length < 8) return "N/A"; + + try { + long user = Long.parseLong(parts[1]); + long nice = Long.parseLong(parts[2]); + long system = Long.parseLong(parts[3]); + long idle = Long.parseLong(parts[4]); + long iowait = Long.parseLong(parts[5]); + long irq = Long.parseLong(parts[6]); + long softirq = Long.parseLong(parts[7]); + long steal = parts.length > 8 ? Long.parseLong(parts[8]) : 0; + + long total = user + nice + system + idle + iowait + irq + softirq + steal; + + if (sPrevTotal != -1 && total != sPrevTotal) { + long diffTotal = total - sPrevTotal; + long diffIdle = idle - sPrevIdle; + long usage = 100 * (diffTotal - diffIdle) / diffTotal; + sPrevTotal = total; + sPrevIdle = idle; + return String.valueOf(usage); + } else { + + sPrevTotal = total; + sPrevIdle = idle; + return "N/A"; + } + } catch (NumberFormatException e) { + return "N/A"; + } + } + + public static List getCpuFrequencies() { + List result = new ArrayList<>(); + String cpuDirPath = "/sys/devices/system/cpu/"; + java.io.File cpuDir = new java.io.File(cpuDirPath); + java.io.File[] files = cpuDir.listFiles((dir, name) -> name.matches("cpu\\d+")); + if (files == null || files.length == 0) { + return result; + } + + List cpuFolders = new ArrayList<>(); + Collections.addAll(cpuFolders, files); + cpuFolders.sort(Comparator.comparingInt(GameBarCpuInfo::extractCpuNumber)); + + for (java.io.File cpu : cpuFolders) { + String freqPath = cpu.getAbsolutePath() + "/cpufreq/scaling_cur_freq"; + String freqStr = readLine(freqPath); + if (freqStr != null && !freqStr.isEmpty()) { + try { + int khz = Integer.parseInt(freqStr.trim()); + int mhz = khz / 1000; + result.add(cpu.getName() + ": " + mhz + " MHz"); + } catch (NumberFormatException e) { + result.add(cpu.getName() + ": N/A"); + } + } else { + result.add(cpu.getName() + ": offline or frequency not available"); + } + } + return result; + } + + public static String getCpuTemp() { + String line = readLine(CPU_TEMP_PATH); + if (line == null) return "N/A"; + line = line.trim(); + try { + float raw = Float.parseFloat(line); + float c = raw / 1000f; + return String.format("%.1f", c); + } catch (NumberFormatException e) { + return "N/A"; + } + } + + private static int extractCpuNumber(java.io.File cpuFolder) { + String name = cpuFolder.getName().replace("cpu", ""); + try { + return Integer.parseInt(name); + } catch (NumberFormatException e) { + return -1; + } + } + + private static String readLine(String path) { + try (BufferedReader br = new BufferedReader(new FileReader(path))) { + return br.readLine(); + } catch (IOException e) { + return null; + } + } +} diff --git a/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarFpsMeter.java b/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarFpsMeter.java new file mode 100644 index 0000000..5947203 --- /dev/null +++ b/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarFpsMeter.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2025 kenway214 + * + * 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 org.lineageos.device.DeviceSettings.gamebar; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Build; +import android.view.WindowManager; +import android.window.TaskFpsCallback; + +import androidx.preference.PreferenceManager; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class GameBarFpsMeter { + + private static final float TOLERANCE = 0.1f; + private static final long STALENESS_THRESHOLD_MS = 2000; + private static final long TASK_CHECK_INTERVAL_MS = 1000; + + private static GameBarFpsMeter sInstance; + private final Context mContext; + private final WindowManager mWindowManager; + private final SharedPreferences mPrefs; + private float mCurrentFps = 0f; + private TaskFpsCallback mTaskFpsCallback; + private boolean mCallbackRegistered = false; + private int mCurrentTaskId = -1; + private long mLastFpsUpdateTime = System.currentTimeMillis(); + private final android.os.Handler mHandler = new android.os.Handler(); + + public static synchronized GameBarFpsMeter getInstance(Context context) { + if (sInstance == null) { + sInstance = new GameBarFpsMeter(context.getApplicationContext()); + } + return sInstance; + } + + private GameBarFpsMeter(Context context) { + mContext = context; + mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + mTaskFpsCallback = new TaskFpsCallback() { + @Override + public void onFpsReported(float fps) { + if (fps > 0) { + mCurrentFps = fps; + mLastFpsUpdateTime = System.currentTimeMillis(); + } + } + }; + } + } + + public void start() { + String method = mPrefs.getString("game_bar_fps_method", "new"); + if (!"new".equals(method)) return; + + stop(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + int taskId = getFocusedTaskId(); + if (taskId <= 0) { + return; + } + mCurrentTaskId = taskId; + try { + mWindowManager.registerTaskFpsCallback(mCurrentTaskId, Runnable::run, mTaskFpsCallback); + mCallbackRegistered = true; + } catch (Exception e) { + } + mLastFpsUpdateTime = System.currentTimeMillis(); + mHandler.postDelayed(mTaskCheckRunnable, TASK_CHECK_INTERVAL_MS); + } + } + + public void stop() { + String method = mPrefs.getString("game_bar_fps_method", "new"); + if ("new".equals(method) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + if (mCallbackRegistered) { + try { + mWindowManager.unregisterTaskFpsCallback(mTaskFpsCallback); + } catch (Exception e) { + } + mCallbackRegistered = false; + } + mHandler.removeCallbacks(mTaskCheckRunnable); + } + } + + public float getFps() { + String method = mPrefs.getString("game_bar_fps_method", "new"); + if ("legacy".equals(method)) { + return readLegacyFps(); + } else { + return mCurrentFps; + } + } + + private float readLegacyFps() { + try (BufferedReader br = new BufferedReader(new FileReader("/sys/class/drm/sde-crtc-0/measured_fps"))) { + String line = br.readLine(); + if (line != null && line.startsWith("fps:")) { + String[] parts = line.split("\\s+"); + if (parts.length >= 2) { + return Float.parseFloat(parts[1].trim()); + } + } + } catch (IOException | NumberFormatException e) { + } + return -1f; + } + + private final Runnable mTaskCheckRunnable = new Runnable() { + @Override + public void run() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + int newTaskId = getFocusedTaskId(); + if (newTaskId > 0 && newTaskId != mCurrentTaskId) { + reinitCallback(); + } else { + long now = System.currentTimeMillis(); + if (now - mLastFpsUpdateTime > STALENESS_THRESHOLD_MS) { + reinitCallback(); + } + } + mHandler.postDelayed(this, TASK_CHECK_INTERVAL_MS); + } + } + }; + + private int getFocusedTaskId() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + return -1; + } + try { + Class atmClass = Class.forName("android.app.ActivityTaskManager"); + Method getServiceMethod = atmClass.getDeclaredMethod("getService"); + Object atmService = getServiceMethod.invoke(null); + Method getFocusedRootTaskInfoMethod = atmService.getClass().getMethod("getFocusedRootTaskInfo"); + Object taskInfo = getFocusedRootTaskInfoMethod.invoke(atmService); + if (taskInfo != null) { + try { + Field taskIdField = taskInfo.getClass().getField("taskId"); + return taskIdField.getInt(taskInfo); + } catch (NoSuchFieldException nsfe) { + try { + Field taskIdField = taskInfo.getClass().getField("mTaskId"); + return taskIdField.getInt(taskInfo); + } catch (NoSuchFieldException nsfe2) { + } + } + } + } catch (Exception e) { + } + return -1; + } + + private void reinitCallback() { + stop(); + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + start(); + } + }, 500); + } +} diff --git a/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarFragment.java b/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarFragment.java new file mode 100644 index 0000000..5638572 --- /dev/null +++ b/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarFragment.java @@ -0,0 +1,394 @@ +/* + * Copyright (C) 2025 kenway214 + * + * 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 org.lineageos.device.DeviceSettings.gamebar; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.provider.Settings; +import android.widget.Toast; + +import androidx.preference.ListPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.SeekBarPreference; +import androidx.preference.SwitchPreference; +import androidx.preference.SwitchPreferenceCompat; + +import com.android.settingslib.widget.MainSwitchPreference; + +import org.lineageos.device.DeviceSettings.R; + +public class GameBarFragment extends PreferenceFragmentCompat { + + private GameBar mGameBar; + private MainSwitchPreference mMasterSwitch; + private SwitchPreferenceCompat mAutoEnableSwitch; + private SwitchPreferenceCompat mFpsSwitch; + private SwitchPreferenceCompat mBatteryTempSwitch; + private SwitchPreferenceCompat mCpuUsageSwitch; + private SwitchPreferenceCompat mCpuClockSwitch; + private SwitchPreferenceCompat mCpuTempSwitch; + private SwitchPreferenceCompat mRamSwitch; + private SwitchPreferenceCompat mGpuUsageSwitch; + private SwitchPreferenceCompat mGpuClockSwitch; + private SwitchPreferenceCompat mGpuTempSwitch; + private Preference mCaptureStartPref; + private Preference mCaptureStopPref; + private Preference mCaptureExportPref; + private SwitchPreferenceCompat mDoubleTapCapturePref; + private SwitchPreferenceCompat mSingleTapTogglePref; + private SwitchPreferenceCompat mLongPressEnablePref; + private ListPreference mLongPressTimeoutPref; + private SeekBarPreference mTextSizePref; + private SeekBarPreference mBgAlphaPref; + private SeekBarPreference mCornerRadiusPref; + private SeekBarPreference mPaddingPref; + private SeekBarPreference mItemSpacingPref; + private ListPreference mUpdateIntervalPref; + private ListPreference mTextColorPref; + private ListPreference mTitleColorPref; + private ListPreference mValueColorPref; + private ListPreference mPositionPref; + private ListPreference mSplitModePref; + private ListPreference mOverlayFormatPref; + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + setPreferencesFromResource(R.xml.game_bar_preferences, rootKey); + + mGameBar = GameBar.getInstance(getContext()); + + // Initialize all preferences. + mMasterSwitch = findPreference("game_bar_enable"); + mAutoEnableSwitch = findPreference("game_bar_auto_enable"); + mFpsSwitch = findPreference("game_bar_fps_enable"); + mBatteryTempSwitch = findPreference("game_bar_temp_enable"); + mCpuUsageSwitch = findPreference("game_bar_cpu_usage_enable"); + mCpuClockSwitch = findPreference("game_bar_cpu_clock_enable"); + mCpuTempSwitch = findPreference("game_bar_cpu_temp_enable"); + mRamSwitch = findPreference("game_bar_ram_enable"); + mGpuUsageSwitch = findPreference("game_bar_gpu_usage_enable"); + mGpuClockSwitch = findPreference("game_bar_gpu_clock_enable"); + mGpuTempSwitch = findPreference("game_bar_gpu_temp_enable"); + + mCaptureStartPref = findPreference("game_bar_capture_start"); + mCaptureStopPref = findPreference("game_bar_capture_stop"); + mCaptureExportPref = findPreference("game_bar_capture_export"); + + mDoubleTapCapturePref = findPreference("game_bar_doubletap_capture"); + mSingleTapTogglePref = findPreference("game_bar_single_tap_toggle"); + mLongPressEnablePref = findPreference("game_bar_longpress_enable"); + mLongPressTimeoutPref = findPreference("game_bar_longpress_timeout"); + + mTextSizePref = findPreference("game_bar_text_size"); + mBgAlphaPref = findPreference("game_bar_background_alpha"); + mCornerRadiusPref = findPreference("game_bar_corner_radius"); + mPaddingPref = findPreference("game_bar_padding"); + mItemSpacingPref = findPreference("game_bar_item_spacing"); + + mUpdateIntervalPref = findPreference("game_bar_update_interval"); + mTextColorPref = findPreference("game_bar_text_color"); + mTitleColorPref = findPreference("game_bar_title_color"); + mValueColorPref = findPreference("game_bar_value_color"); + mPositionPref = findPreference("game_bar_position"); + mSplitModePref = findPreference("game_bar_split_mode"); + mOverlayFormatPref = findPreference("game_bar_format"); + + Preference appSelectorPref = findPreference("game_bar_app_selector"); + if (appSelectorPref != null) { + appSelectorPref.setOnPreferenceClickListener(pref -> { + Intent intent = new Intent(getContext(), GameBarAppSelectorActivity.class); + startActivity(intent); + return true; + }); + } + Preference appRemoverPref = findPreference("game_bar_app_remover"); + if (appRemoverPref != null) { + appRemoverPref.setOnPreferenceClickListener(pref -> { + Intent intent = new Intent(getContext(), GameBarAppRemoverActivity.class); + startActivity(intent); + return true; + }); + } + + if (mMasterSwitch != null) { + mMasterSwitch.setOnPreferenceChangeListener((pref, newValue) -> { + boolean enabled = (boolean) newValue; + if (enabled) { + if (Settings.canDrawOverlays(getContext())) { + mGameBar.applyPreferences(); + mGameBar.show(); + getContext().startService(new Intent(getContext(), GameBarMonitorService.class)); + } else { + Toast.makeText(getContext(), R.string.overlay_permission_required, Toast.LENGTH_SHORT).show(); + return false; + } + } else { + mGameBar.hide(); + if (mAutoEnableSwitch == null || !mAutoEnableSwitch.isChecked()) { + getContext().stopService(new Intent(getContext(), GameBarMonitorService.class)); + } + } + return true; + }); + } + + if (mAutoEnableSwitch != null) { + mAutoEnableSwitch.setOnPreferenceChangeListener((pref, newValue) -> { + boolean autoEnabled = (boolean) newValue; + if (autoEnabled) { + getContext().startService(new Intent(getContext(), GameBarMonitorService.class)); + } else { + if (mMasterSwitch == null || !mMasterSwitch.isChecked()) { + getContext().stopService(new Intent(getContext(), GameBarMonitorService.class)); + } + } + return true; + }); + } + + if (mFpsSwitch != null) { + mFpsSwitch.setOnPreferenceChangeListener((pref, newValue) -> { + mGameBar.setShowFps((boolean) newValue); + return true; + }); + } + if (mBatteryTempSwitch != null) { + mBatteryTempSwitch.setOnPreferenceChangeListener((pref, newValue) -> { + mGameBar.setShowBatteryTemp((boolean) newValue); + return true; + }); + } + if (mCpuUsageSwitch != null) { + mCpuUsageSwitch.setOnPreferenceChangeListener((pref, newValue) -> { + mGameBar.setShowCpuUsage((boolean) newValue); + return true; + }); + } + if (mCpuClockSwitch != null) { + mCpuClockSwitch.setOnPreferenceChangeListener((pref, newValue) -> { + mGameBar.setShowCpuClock((boolean) newValue); + return true; + }); + } + if (mCpuTempSwitch != null) { + mCpuTempSwitch.setOnPreferenceChangeListener((pref, newValue) -> { + mGameBar.setShowCpuTemp((boolean) newValue); + return true; + }); + } + if (mRamSwitch != null) { + mRamSwitch.setOnPreferenceChangeListener((pref, newValue) -> { + mGameBar.setShowRam((boolean) newValue); + return true; + }); + } + if (mGpuUsageSwitch != null) { + mGpuUsageSwitch.setOnPreferenceChangeListener((pref, newValue) -> { + mGameBar.setShowGpuUsage((boolean) newValue); + return true; + }); + } + if (mGpuClockSwitch != null) { + mGpuClockSwitch.setOnPreferenceChangeListener((pref, newValue) -> { + mGameBar.setShowGpuClock((boolean) newValue); + return true; + }); + } + if (mGpuTempSwitch != null) { + mGpuTempSwitch.setOnPreferenceChangeListener((pref, newValue) -> { + mGameBar.setShowGpuTemp((boolean) newValue); + return true; + }); + } + if (mCaptureStartPref != null) { + mCaptureStartPref.setOnPreferenceClickListener(pref -> { + GameDataExport.getInstance().startCapture(); + Toast.makeText(getContext(), "Started logging Data", Toast.LENGTH_SHORT).show(); + return true; + }); + } + if (mCaptureStopPref != null) { + mCaptureStopPref.setOnPreferenceClickListener(pref -> { + GameDataExport.getInstance().stopCapture(); + Toast.makeText(getContext(), "Stopped logging Data", Toast.LENGTH_SHORT).show(); + return true; + }); + } + if (mCaptureExportPref != null) { + mCaptureExportPref.setOnPreferenceClickListener(pref -> { + GameDataExport.getInstance().exportDataToCsv(); + Toast.makeText(getContext(), "Exported log data to file", Toast.LENGTH_SHORT).show(); + return true; + }); + } + if (mDoubleTapCapturePref != null) { + mDoubleTapCapturePref.setOnPreferenceChangeListener((pref, newValue) -> { + mGameBar.setDoubleTapCaptureEnabled((boolean) newValue); + return true; + }); + } + if (mSingleTapTogglePref != null) { + mSingleTapTogglePref.setOnPreferenceChangeListener((pref, newValue) -> { + mGameBar.setSingleTapToggleEnabled((boolean) newValue); + return true; + }); + } + if (mLongPressEnablePref != null) { + mLongPressEnablePref.setOnPreferenceChangeListener((pref, newValue) -> { + mGameBar.setLongPressEnabled((boolean) newValue); + return true; + }); + } + if (mLongPressTimeoutPref != null) { + mLongPressTimeoutPref.setOnPreferenceChangeListener((pref, newValue) -> { + if (newValue instanceof String) { + long ms = Long.parseLong((String) newValue); + mGameBar.setLongPressThresholdMs(ms); + } + return true; + }); + } + if (mTextSizePref != null) { + mTextSizePref.setOnPreferenceChangeListener((pref, newValue) -> { + if (newValue instanceof Integer) { + mGameBar.updateTextSize((Integer) newValue); + } + return true; + }); + } + if (mBgAlphaPref != null) { + mBgAlphaPref.setOnPreferenceChangeListener((pref, newValue) -> { + if (newValue instanceof Integer) { + mGameBar.updateBackgroundAlpha((Integer) newValue); + } + return true; + }); + } + if (mCornerRadiusPref != null) { + mCornerRadiusPref.setOnPreferenceChangeListener((pref, newValue) -> { + if (newValue instanceof Integer) { + mGameBar.updateCornerRadius((Integer) newValue); + } + return true; + }); + } + if (mPaddingPref != null) { + mPaddingPref.setOnPreferenceChangeListener((pref, newValue) -> { + if (newValue instanceof Integer) { + mGameBar.updatePadding((Integer) newValue); + } + return true; + }); + } + if (mItemSpacingPref != null) { + mItemSpacingPref.setOnPreferenceChangeListener((pref, newValue) -> { + if (newValue instanceof Integer) { + mGameBar.updateItemSpacing((Integer) newValue); + } + return true; + }); + } + if (mUpdateIntervalPref != null) { + mUpdateIntervalPref.setOnPreferenceChangeListener((pref, newValue) -> { + if (newValue instanceof String) { + mGameBar.updateUpdateInterval((String) newValue); + } + return true; + }); + } + if (mTextColorPref != null) { + mTextColorPref.setOnPreferenceChangeListener((pref, newValue) -> true); + } + if (mTitleColorPref != null) { + mTitleColorPref.setOnPreferenceChangeListener((pref, newValue) -> { + if (newValue instanceof String) { + mGameBar.updateTitleColor((String) newValue); + } + return true; + }); + } + if (mValueColorPref != null) { + mValueColorPref.setOnPreferenceChangeListener((pref, newValue) -> { + if (newValue instanceof String) { + mGameBar.updateValueColor((String) newValue); + } + return true; + }); + } + if (mPositionPref != null) { + mPositionPref.setOnPreferenceChangeListener((pref, newValue) -> { + if (newValue instanceof String) { + mGameBar.updatePosition((String) newValue); + } + return true; + }); + } + if (mSplitModePref != null) { + mSplitModePref.setOnPreferenceChangeListener((pref, newValue) -> { + if (newValue instanceof String) { + mGameBar.updateSplitMode((String) newValue); + } + return true; + }); + } + if (mOverlayFormatPref != null) { + mOverlayFormatPref.setOnPreferenceChangeListener((pref, newValue) -> { + if (newValue instanceof String) { + mGameBar.updateOverlayFormat((String) newValue); + } + return true; + }); + } + } + + @Override + public void onResume() { + super.onResume(); + if (!hasUsageStatsPermission(requireContext())) { + requestUsageStatsPermission(); + } + Context context = getContext(); + if (context != null) { + if ((mMasterSwitch != null && mMasterSwitch.isChecked()) || + (mAutoEnableSwitch != null && mAutoEnableSwitch.isChecked())) { + context.startService(new Intent(context, GameBarMonitorService.class)); + } else { + context.stopService(new Intent(context, GameBarMonitorService.class)); + } + } + } + + private boolean hasUsageStatsPermission(Context context) { + android.app.AppOpsManager appOps = (android.app.AppOpsManager) + context.getSystemService(Context.APP_OPS_SERVICE); + if (appOps == null) return false; + int mode = appOps.checkOpNoThrow( + android.app.AppOpsManager.OPSTR_GET_USAGE_STATS, + android.os.Process.myUid(), + context.getPackageName() + ); + return (mode == android.app.AppOpsManager.MODE_ALLOWED); + } + + private void requestUsageStatsPermission() { + Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS); + startActivity(intent); + } +} + diff --git a/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarGpuInfo.java b/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarGpuInfo.java new file mode 100644 index 0000000..afee061 --- /dev/null +++ b/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarGpuInfo.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2025 kenway214 + * + * 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 org.lineageos.device.DeviceSettings.gamebar; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; + +public class GameBarGpuInfo { + + private static final String GPU_USAGE_PATH = "/sys/class/kgsl/kgsl-3d0/gpu_busy_percentage"; + private static final String GPU_CLOCK_PATH = "/sys/class/kgsl/kgsl-3d0/gpuclk"; + private static final String GPU_TEMP_PATH = "/sys/class/kgsl/kgsl-3d0/temp"; + + public static String getGpuUsage() { + String line = readLine(GPU_USAGE_PATH); + if (line == null) { + return "N/A"; + } + line = line.replace("%", "").trim(); + try { + int val = Integer.parseInt(line); + return String.valueOf(val); + } catch (NumberFormatException e) { + return "N/A"; + } + } + + public static String getGpuClock() { + String line = readLine(GPU_CLOCK_PATH); + if (line == null) { + return "N/A"; + } + line = line.trim(); + try { + long hz = Long.parseLong(line); + long mhz = hz / 1_000_000; + return String.valueOf(mhz); + } catch (NumberFormatException e) { + return "N/A"; + } + } + + public static String getGpuTemp() { + String line = readLine(GPU_TEMP_PATH); + if (line == null) { + return "N/A"; + } + line = line.trim(); + try { + float raw = Float.parseFloat(line); + float c = raw / 1000f; + return String.format("%.1f", c); + } catch (NumberFormatException e) { + return "N/A"; + } + } + + private static String readLine(String path) { + try (BufferedReader br = new BufferedReader(new FileReader(path))) { + return br.readLine(); + } catch (IOException e) { + return null; + } + } +} diff --git a/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarMemInfo.java b/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarMemInfo.java new file mode 100644 index 0000000..a2fcf9f --- /dev/null +++ b/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarMemInfo.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2025 kenway214 + * + * 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 org.lineageos.device.DeviceSettings.gamebar; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; + +public class GameBarMemInfo { + + public static String getRamUsage() { + long memTotal = 0; + long memAvailable = 0; + + try (BufferedReader br = new BufferedReader(new FileReader("/proc/meminfo"))) { + String line; + while ((line = br.readLine()) != null) { + if (line.startsWith("MemTotal:")) { + memTotal = parseMemValue(line); + } else if (line.startsWith("MemAvailable:")) { + memAvailable = parseMemValue(line); + } + if (memTotal > 0 && memAvailable > 0) { + break; + } + } + } catch (IOException e) { + return "N/A"; + } + + if (memTotal == 0) { + return "N/A"; + } + + long usedKb = (memTotal - memAvailable); + long usedMb = usedKb / 1024; + return String.valueOf(usedMb); + } + + private static long parseMemValue(String line) { + String[] parts = line.split("\\s+"); + if (parts.length < 3) { + return 0; + } + try { + return Long.parseLong(parts[1]); + } catch (NumberFormatException e) { + return 0; + } + } +} diff --git a/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarMonitorService.java b/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarMonitorService.java new file mode 100644 index 0000000..c00e9c4 --- /dev/null +++ b/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarMonitorService.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2025 kenway214 + * + * 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 org.lineageos.device.DeviceSettings.gamebar; + +import android.app.Service; +import android.content.Intent; +import android.os.Handler; +import android.os.IBinder; +import androidx.preference.PreferenceManager; +import java.util.HashSet; +import java.util.Set; + +public class GameBarMonitorService extends Service { + + private Handler mHandler; + private Runnable mMonitorRunnable; + private static final long MONITOR_INTERVAL = 2000; // 2 seconds + + @Override + public void onCreate() { + super.onCreate(); + mHandler = new Handler(); + mMonitorRunnable = new Runnable() { + @Override + public void run() { + monitorForegroundApp(); + mHandler.postDelayed(this, MONITOR_INTERVAL); + } + }; + mHandler.post(mMonitorRunnable); + } + + private void monitorForegroundApp() { + var prefs = PreferenceManager.getDefaultSharedPreferences(this); + boolean masterEnabled = prefs.getBoolean("game_bar_enable", false); + if (masterEnabled) { + GameBar.getInstance(this).applyPreferences(); + GameBar.getInstance(this).show(); + return; + } + + boolean autoEnabled = prefs.getBoolean("game_bar_auto_enable", false); + if (!autoEnabled) { + GameBar.getInstance(this).hide(); + return; + } + + String foreground = ForegroundAppDetector.getForegroundPackageName(this); + Set autoApps = prefs.getStringSet(GameBarAppSelectorFragment.PREF_AUTO_APPS, new HashSet<>()); + if (autoApps.contains(foreground)) { + GameBar.getInstance(this).applyPreferences(); + GameBar.getInstance(this).show(); + } else { + GameBar.getInstance(this).hide(); + } + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public void onDestroy() { + super.onDestroy(); + mHandler.removeCallbacks(mMonitorRunnable); + } +} diff --git a/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarSettingsActivity.java b/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarSettingsActivity.java new file mode 100644 index 0000000..8972306 --- /dev/null +++ b/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarSettingsActivity.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2025 kenway214 + * + * 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 org.lineageos.device.DeviceSettings.gamebar; + +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.provider.Settings; +import android.widget.Toast; + +import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity; + +import org.lineageos.device.DeviceSettings.R; + +public class GameBarSettingsActivity extends CollapsingToolbarBaseActivity { + private static final int OVERLAY_PERMISSION_REQUEST_CODE = 1234; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_game_bar); + setTitle(getString(R.string.game_bar_title)); + + if (!Settings.canDrawOverlays(this)) { + Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, + Uri.parse("package:" + getPackageName())); + startActivityForResult(intent, OVERLAY_PERMISSION_REQUEST_CODE); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == OVERLAY_PERMISSION_REQUEST_CODE) { + if (Settings.canDrawOverlays(this)) { + Toast.makeText(this, R.string.overlay_permission_granted, Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(this, R.string.overlay_permission_denied, Toast.LENGTH_SHORT).show(); + } + } + } +} diff --git a/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarTileService.java b/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarTileService.java new file mode 100644 index 0000000..e3db7b8 --- /dev/null +++ b/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameBarTileService.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2025 kenway214 + * + * 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 org.lineageos.device.DeviceSettings.gamebar; + +import android.service.quicksettings.Tile; +import android.service.quicksettings.TileService; + +import androidx.preference.PreferenceManager; + +import org.lineageos.device.DeviceSettings.R; + +public class GameBarTileService extends TileService { + private GameBar mGameBar; + + @Override + public void onCreate() { + super.onCreate(); + mGameBar = GameBar.getInstance(this); + } + + @Override + public void onStartListening() { + boolean enabled = PreferenceManager.getDefaultSharedPreferences(this) + .getBoolean("game_bar_enable", false); + updateTileState(enabled); + } + + @Override + public void onClick() { + boolean currentlyEnabled = PreferenceManager.getDefaultSharedPreferences(this) + .getBoolean("game_bar_enable", false); + boolean newState = !currentlyEnabled; + + PreferenceManager.getDefaultSharedPreferences(this) + .edit() + .putBoolean("game_bar_enable", newState) + .commit(); + + updateTileState(newState); + + if (newState) { + mGameBar.applyPreferences(); + mGameBar.show(); + } else { + mGameBar.hide(); + } + } + + private void updateTileState(boolean enabled) { + Tile tile = getQsTile(); + if (tile == null) return; + + tile.setState(enabled ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE); + tile.setLabel(getString(R.string.game_bar_tile_label)); + tile.setContentDescription(getString(R.string.game_bar_tile_description)); + tile.updateTile(); + } +} diff --git a/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameDataExport.java b/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameDataExport.java new file mode 100644 index 0000000..e604f5b --- /dev/null +++ b/DeviceSettings/src/org/lineageos/device/DeviceSettings/gamebar/GameDataExport.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2025 kenway214 + * + * 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 org.lineageos.device.DeviceSettings.gamebar; + +import android.os.Environment; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +public class GameDataExport { + + private static GameDataExport sInstance; + public static synchronized GameDataExport getInstance() { + if (sInstance == null) { + sInstance = new GameDataExport(); + } + return sInstance; + } + + private boolean mCapturing = false; + + private final List mStatsRows = new ArrayList<>(); + + private static final String[] CSV_HEADER = { + "DateTime", + "PackageName", + "FPS", + "Battery_Temp", + "CPU_Usage", + "CPU_Temp", + "GPU_Usage", + "GPU_Clock", + "GPU_Temp" + }; + + private GameDataExport() { + } + + public void startCapture() { + mCapturing = true; + mStatsRows.clear(); + mStatsRows.add(CSV_HEADER); + } + + public void stopCapture() { + mCapturing = false; + } + + public boolean isCapturing() { + return mCapturing; + } + + public void addOverlayData(String dateTime, + String packageName, + String fps, + String batteryTemp, + String cpuUsage, + String cpuTemp, + String gpuUsage, + String gpuClock, + String gpuTemp) { + if (!mCapturing) return; + + String[] row = { + dateTime, + packageName, + fps, + batteryTemp, + cpuUsage, + cpuTemp, + gpuUsage, + gpuClock, + gpuTemp + }; + mStatsRows.add(row); + } + + public void exportDataToCsv() { + if (mStatsRows.size() <= 1) { + return; + } + String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date()); + File outFile = new File(Environment.getExternalStorageDirectory(), "GameBar_log_" + timeStamp + ".csv"); + + BufferedWriter bw = null; + try { + bw = new BufferedWriter(new FileWriter(outFile, true)); + for (String[] row : mStatsRows) { + bw.write(toCsvLine(row)); + bw.newLine(); + } + bw.flush(); + } catch (IOException ignored) { + } finally { + if (bw != null) { + try { bw.close(); } catch (IOException ignored) {} + } + } + } + + private String toCsvLine(String[] columns) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < columns.length; i++) { + sb.append(columns[i]); + if (i < columns.length - 1) { + sb.append(","); + } + } + return sb.toString(); + } +} diff --git a/DeviceSettings/src/org/lineageos/device/DeviceSettings/utils/FileUtils.java b/DeviceSettings/src/org/lineageos/device/DeviceSettings/utils/FileUtils.java new file mode 100644 index 0000000..2024f42 --- /dev/null +++ b/DeviceSettings/src/org/lineageos/device/DeviceSettings/utils/FileUtils.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2016 The CyanogenMod Project + * + * 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 org.lineageos.device.DeviceSettings.utils; + +import android.util.Log; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; + +public final class FileUtils { + private static final String TAG = "FileUtils"; + + private FileUtils() { + // This class is not supposed to be instantiated + } + + /** + * 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; + } + + /** + * Writes the given value into the given file + * + * @return true on success, false on failure + */ + public static boolean writeLine(String fileName, String value) { + BufferedWriter writer = null; + + try { + writer = new BufferedWriter(new FileWriter(fileName)); + writer.write(value); + } catch (FileNotFoundException e) { + Log.w(TAG, "No such file " + fileName + " for writing", e); + return false; + } catch (IOException e) { + Log.e(TAG, "Could not write to file " + fileName, e); + return false; + } finally { + try { + if (writer != null) { + writer.close(); + } + } catch (IOException e) { + // Ignored, not much we can do anyway + } + } + + return true; + } + + /** + * 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(); + } + + /** + * Deletes an existing file + * + * @return true if the delete was successful, false if not + */ + public static boolean delete(String fileName) { + final File file = new File(fileName); + boolean ok = false; + try { + ok = file.delete(); + } catch (SecurityException e) { + Log.w(TAG, "SecurityException trying to delete " + fileName, e); + } + return ok; + } + + /** + * Renames an existing file + * + * @return true if the rename was successful, false if not + */ + public static boolean rename(String srcPath, String dstPath) { + final File srcFile = new File(srcPath); + final File dstFile = new File(dstPath); + boolean ok = false; + try { + ok = srcFile.renameTo(dstFile); + } catch (SecurityException e) { + Log.w(TAG, "SecurityException trying to rename " + srcPath + " to " + dstPath, e); + } catch (NullPointerException e) { + Log.e(TAG, "NullPointerException trying to rename " + srcPath + " to " + dstPath, e); + } + return ok; + } + + public static boolean getFileValueAsBoolean(String filename, boolean defValue) { + String fileValue = readOneLine(filename); + if(fileValue!=null){ + return (fileValue.equals("0")?false:true); + } + return defValue; + } + + public static String getFileValue(String filename, String defValue) { + String fileValue = readOneLine(filename); + if(fileValue!=null){ + return fileValue; + } + return defValue; + } +} diff --git a/product.prop b/product.prop index 0427df9..87542e8 100644 --- a/product.prop +++ b/product.prop @@ -5,8 +5,5 @@ audio.offload.min.duration.secs=30 audio.offload.video=true ro.af.client_heap_size_kbyte=7168 -# Device name -ro.product.marketname=OnePlus 11 5G - # GMS ro.opa.device_model_id=ga-oplus-skill-os121-211011