Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7d5c7c56 authored by Manu Suresh's avatar Manu Suresh Committed by Sahil Sonar
Browse files

FP5: introduce RefreshRateController priv-app for setting refresh rate

parent 8f65a640
Loading
Loading
Loading
Loading

display/Android.bp

0 → 100644
+13 −0
Original line number Original line Diff line number Diff line
//
// Copyright (C) 2025 E FOUNDATION
// SPDX-License-Identifier: Apache-2.0
//

android_app {
    name: "RefreshRateController",
    srcs: ["src/**/*.java"],
    certificate: "platform",
    platform_apis: true,
    privileged: true,
    system_ext_specific: true,
}
+28 −0
Original line number Original line Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="foundation.e.refreshratecontroller"
    android:sharedUserId="android.uid.system">

    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
    <uses-permission android:name="android.permission.WRITE_SETTINGS"/>

    <application
        android:label="RefreshRateController"
        android:persistent="true"
        android:directBootAware="true"
        android:defaultToDeviceProtectedStorage="true">

        <receiver
            android:name=".RefreshRateBroadcastReceiver"
            android:exported="false">
            <intent-filter>
                <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED"/>
            </intent-filter>
        </receiver>

        <service
            android:name=".RefreshRateMonitoringService"
            android:exported="false"/>

    </application>
</manifest>
+27 −0
Original line number Original line Diff line number Diff line
package foundation.e.refreshratecontroller;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

public class RefreshRateBroadcastReceiver extends BroadcastReceiver {

    private static final String TAG = "RefreshRateBroadcastReceiver";

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (action == null) {
            return;
        }

        Log.d(TAG, "Received action: " + action);

        if (action.equals(Intent.ACTION_LOCKED_BOOT_COMPLETED)) {
            Log.d(TAG, "Starting RefreshRateMonitoringService...");
            Intent serviceIntent = new Intent(context, RefreshRateMonitoringService.class);
            context.startService(serviceIntent);
        }
    }
}
+233 −0
Original line number Original line Diff line number Diff line
package foundation.e.refreshratecontroller;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.hardware.display.DisplayManager;
import android.os.Handler;
import android.os.IBinder;
import android.provider.Settings;
import android.util.Log;
import android.view.Display;

public class RefreshRateMonitoringService extends Service implements DisplayManager.DisplayListener {

    private static final String TAG = "RefreshRateMonitoringService";
    private static final String MIN_REFRESH_RATE_KEY = Settings.System.MIN_REFRESH_RATE;
    private static final long APPLY_DELAY_MILLIS = 250;
    private static final float MIN_FPS = 0f;
    private static final float MAX_FPS = 90.0f;

    private int currentDisplayState = Display.STATE_UNKNOWN;
    private int currentDisplayModeId = Display.Mode.INVALID_MODE_ID;
    private float preferredMinFPS = MIN_FPS;
    private boolean isRefreshRateSwitchInProgress = false;

    private DisplayManager displayManager;
    private Handler handler;

    // RefreshRateMonitoringService class methods

    private void setSystemSettingFloat(String key, float value) {
        try {
            boolean setResult = Settings.System.putFloat(getContentResolver(), key, value);
            if (setResult) {
                Log.d(TAG, "Successfully set '" + key + "' to " + value);
            } else {
                Log.e(TAG, "Failed to set '" + key + "'.");
            }
        } catch (SecurityException e) {
            Log.e(TAG, "Permission denied for key '" + key + "'.", e);
        } catch (Exception e) {
            Log.e(TAG, "Unexpected error while modifying '" + key + "'.", e);
        }
    }
    private void resetRefreshRateOverrides() {
        Log.d(TAG, "Setting MIN_REFRESH_RATE_KEY to " + MIN_FPS);
        setSystemSettingFloat(MIN_REFRESH_RATE_KEY, MIN_FPS);
    }

    private void applyDesiredRefreshRateOverrides() {
        Log.d(TAG, "Setting MIN_REFRESH_RATE_KEY to " + preferredMinFPS);
        setSystemSettingFloat(MIN_REFRESH_RATE_KEY, preferredMinFPS);
    }

    private Runnable applyDesiredRefreshRatesRunnable = new Runnable() {
        @Override
        public void run() {
            Log.d(TAG, "Executing delayed refresh rate application...");
            applyDesiredRefreshRateOverrides();
            isRefreshRateSwitchInProgress = false;
        }
    };

    private String displayStateToString(int state) {
        switch (state) {
            case Display.STATE_OFF: return "STATE_OFF";
            case Display.STATE_ON: return "STATE_ON";
            case Display.STATE_DOZE: return "STATE_DOZE";
            case Display.STATE_DOZE_SUSPEND: return "STATE_DOZE_SUSPEND";
            case Display.STATE_UNKNOWN: return "STATE_UNKNOWN";
            default: return "UNKNOWN_STATE(" + state + ")";
        }
    }

    private void logDisplayState(Display display) {
        if (display != null) {
            Log.d(TAG, "Display " + display.getDisplayId() + " state: " + displayStateToString(display.getState()));
        }
    }

    private void logDisplayInfo(Display display) {
        if (display != null) {
            Display.Mode currentMode = display.getMode();
            Log.d(TAG, "Display ID=" + display.getDisplayId() +
                    ", Mode=" + currentMode.getModeId() +
                    ", Width=" + currentMode.getPhysicalWidth() +
                    ", Height=" + currentMode.getPhysicalHeight() +
                    ", RefreshRate=" + currentMode.getRefreshRate());
        }
    }

    private void initializeDisplay() {
        Display defaultDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
        if (defaultDisplay != null) {
            currentDisplayState = defaultDisplay.getState();
            currentDisplayModeId = defaultDisplay.getMode().getModeId();

            preferredMinFPS = getMinRefreshRateFromKey("onCreate");
            if (preferredMinFPS == Float.POSITIVE_INFINITY) {
                preferredMinFPS = MAX_FPS;
            } else if (preferredMinFPS == MIN_FPS) {
                preferredMinFPS = defaultDisplay.getMode().getRefreshRate();
            }

            logDisplayState(defaultDisplay);
            logDisplayInfo(defaultDisplay);

            isRefreshRateSwitchInProgress = true;
            resetRefreshRateOverrides();
            handler.postDelayed(applyDesiredRefreshRatesRunnable, APPLY_DELAY_MILLIS);
        }
    }

    private void handleDisplayChange(Display display) {
        int newState = display.getState();
        int newModeId = display.getMode().getModeId();
        logDisplayState(display);

        if (newState != currentDisplayState) {
            Log.d(TAG, "Display state changed from " + displayStateToString(currentDisplayState) + " to " + displayStateToString(newState));
            currentDisplayState = newState;

            if (newState == Display.STATE_ON) {
                Log.d(TAG, "Display now ON. Scheduling refresh rate change.");
                isRefreshRateSwitchInProgress = true;
                resetRefreshRateOverrides();
                handler.postDelayed(applyDesiredRefreshRatesRunnable, APPLY_DELAY_MILLIS);
            }
        }

        if (newModeId != currentDisplayModeId) {
            Log.d(TAG, "Display mode changed from " + currentDisplayModeId + " to " + newModeId);
            currentDisplayModeId = newModeId;

            if (newState == Display.STATE_ON && !isRefreshRateSwitchInProgress) {
                preferredMinFPS = getMinRefreshRateFromKey("onDisplayChanged");
                if (preferredMinFPS == Float.POSITIVE_INFINITY) {
                    preferredMinFPS = MAX_FPS;
                }
            }
        }

        logDisplayInfo(display);
    }

    private void resetDisplayState() {
        currentDisplayState = Display.STATE_UNKNOWN;
        currentDisplayModeId = Display.Mode.INVALID_MODE_ID;
        preferredMinFPS = MIN_FPS;
        handler.removeCallbacks(applyDesiredRefreshRatesRunnable);
    }

    private float getMinRefreshRateFromKey(String stage) {
        float currentMinRefreshRateFromKey = Settings.System.getFloat(getContentResolver(), MIN_REFRESH_RATE_KEY, MIN_FPS);
        if (Math.round(currentMinRefreshRateFromKey) == 0) {
            Log.d(TAG, "Stage = " + stage + ": currentMinRefreshRateFromKey not set");
            return MIN_FPS;
        } else {
            Log.d(TAG, "Stage = " + stage + ": currentMinRefreshRateFromKey = " + currentMinRefreshRateFromKey);
            return currentMinRefreshRateFromKey;
        }
    }

    // Service class overrides

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "Service created");
        handler = new Handler(getMainLooper());
        displayManager = (DisplayManager) getSystemService(Context.DISPLAY_SERVICE);
        if (displayManager != null) {
            displayManager.registerDisplayListener(this, handler);
            Log.d(TAG, "DisplayListener registered.");
            initializeDisplay();
        } else {
            Log.e(TAG, "Could not get DisplayManager service.");
        }
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "Service started");
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "Service destroyed");
        handler.removeCallbacks(applyDesiredRefreshRatesRunnable);
        if (displayManager != null) {
            displayManager.unregisterDisplayListener(this);
            Log.d(TAG, "DisplayListener unregistered.");
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    // DisplayManager.DisplayListener overrides

    @Override
    public void onDisplayAdded(int displayId) {
        Log.d(TAG, "Display added: displayId = " + displayId);
        if (displayId == Display.DEFAULT_DISPLAY) {
            initializeDisplay();
        }
    }

    @Override
    public void onDisplayRemoved(int displayId) {
        Log.d(TAG, "Display removed: displayId = " + displayId);
        if (displayId == Display.DEFAULT_DISPLAY) {
            resetDisplayState();
        }
    }
    @Override
    public void onDisplayChanged(int displayId) {
        Log.d(TAG, "Display changed: displayId = " + displayId);
        if (displayId != Display.DEFAULT_DISPLAY) {
            return;
        }
        if (displayManager != null) {
            Display display = displayManager.getDisplay(displayId);
            if (display != null) {
                handleDisplayChange(display);
            }
        }
    }
}