ParanoidSystemUI: Introduce smooth display QS tile

AKA refresh rate tile.
Thanks to WSTxda for the icon.
handleClick() logic inspired by Caffeine tile.

Change-Id: If95d751e01ad77b8afef6a16dce3c933cc95310e
This commit is contained in:
Adithya R 2023-10-18 06:25:13 +05:30
parent 7113e82641
commit 9de4cfca7a
5 changed files with 246 additions and 1 deletions

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:fillType="evenOdd"
android:pathData="M7 3h10v4h2V3c0-1.1-0.9-1.99-2-1.99L7 1C5.9 1 5 1.9 5 3v5 4H2l4 4 4-4H7V3Zm10 18H7v-4H5v4c0 1.1 0.9 2 2 2h10c1.1 0 2-0.9 2-2v-5-4h3l-4-4-4 4h3v0.22V21Z"/>
</vector>

View File

@ -58,4 +58,7 @@
<string name="force_hbm">Force HBM</string>
<string name="auto_brightness_is_off">Auto brightness is off</string>
<!-- Smooth Display -->
<string name="smooth_display">Smooth display</string>
</resources>

View File

@ -8,7 +8,7 @@
<!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
<string name="quick_settings_tiles_stock" translatable="false">
internet,wifi,cell,bt,flashlight,dnd,alarm,airplane,nfc,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream,caffeine,dataswitch,heads_up,dc_dimming,aod,powershare,force_hbm
internet,wifi,cell,bt,flashlight,dnd,alarm,airplane,nfc,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream,caffeine,dataswitch,heads_up,dc_dimming,aod,powershare,force_hbm,smooth_display
</string>
<!-- Determines whether the shell features all run on another thread. -->

View File

@ -27,6 +27,7 @@ import co.aospa.systemui.qs.tiles.DcDimmingTile;
import co.aospa.systemui.qs.tiles.ForceHbmTile;
import co.aospa.systemui.qs.tiles.HeadsUpTile;
import co.aospa.systemui.qs.tiles.PowerShareTile;
import co.aospa.systemui.qs.tiles.SmoothDisplayTile;
import co.aospa.systemui.qs.tiles.WifiTile
import dagger.Binds
import dagger.Module
@ -96,4 +97,10 @@ interface ParanoidQSModule {
@StringKey(ForceHbmTile.TILE_SPEC)
fun bindForceHbmTile(forceHbmTile: ForceHbmTile): QSTileImpl<*>
/** Inject SmoothDisplayTile into tileMap in QSModule */
@Binds
@IntoMap
@StringKey(SmoothDisplayTile.TILE_SPEC)
fun bindSmoothDisplayTile(smoothDisplayTile: SmoothDisplayTile): QSTileImpl<*>
}

View File

@ -0,0 +1,225 @@
package co.aospa.systemui.qs.tiles;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.provider.Settings;
import android.service.quicksettings.Tile;
import android.text.TextUtils;
import android.view.View;
import androidx.annotation.Nullable;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import javax.inject.Inject;
public class SmoothDisplayTile extends QSTileImpl<BooleanState> implements
BatteryController.BatteryStateChangeCallback {
public static final String TILE_SPEC = "smooth_display";
private static final String TAG = "SmoothDisplayTile";
private static final String ACTION_SMOOTH_DISPLAY_SETTINGS =
"android.settings.SMOOTH_DISPLAY_SETTINGS";
private static final int DEFAULT_REFRESH_RATE = 60;
private List<Integer> mHighRefreshRates;
private int mLeastHighRefreshRate, mMaxRefreshRate, mRefreshRateIndex;
public long mLastClickTime = -1;
private final BatteryController mBatteryController;
@Inject
public SmoothDisplayTile(
QSHost host,
@Background Looper backgroundLooper,
@Main Handler mainHandler,
FalsingManager falsingManager,
MetricsLogger metricsLogger,
StatusBarStateController statusBarStateController,
ActivityStarter activityStarter,
QSLogger qsLogger,
BatteryController batteryController
) {
super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController,
activityStarter, qsLogger);
mHighRefreshRates = getHighRefreshRates();
mMaxRefreshRate = Collections.max(mHighRefreshRates);
mLeastHighRefreshRate = Collections.min(mHighRefreshRates);
mBatteryController = batteryController;
batteryController.observe(getLifecycle(), this);
}
@Override
public void onPowerSaveChanged(boolean isPowerSave) {
refreshState();
}
@Override
public boolean isAvailable() {
return mHighRefreshRates.size() > 0;
}
@Override
public BooleanState newTileState() {
return new BooleanState();
}
@Override
protected void handleClick(@Nullable View view) {
if (getState().state == Tile.STATE_UNAVAILABLE) {
return;
}
// If last user clicks < 5 seconds
// we cycle different refresh rates (if we have more than 2)
// otherwise toggle on/off
if (mHighRefreshRates.size() > 1 && mLastClickTime != -1
&& (SystemClock.elapsedRealtime() - mLastClickTime) < 5000) {
// cycle refresh rate
mRefreshRateIndex++;
if (mRefreshRateIndex >= mHighRefreshRates.size()) {
// all refresh rates cycled, turn if off
mRefreshRateIndex = -1;
setPeakRefreshRate(DEFAULT_REFRESH_RATE);
} else {
// change refresh rate
final int refreshRate = mHighRefreshRates.get(mRefreshRateIndex);
setSmoothRefreshRate(refreshRate);
}
} else {
// toggle
if (isSmoothDisplayEnabled()) {
setPeakRefreshRate(DEFAULT_REFRESH_RATE);
mRefreshRateIndex = -1;
} else {
final int refreshRate = getSmoothRefreshRate();
setPeakRefreshRate(refreshRate);
mRefreshRateIndex = mHighRefreshRates.indexOf(refreshRate);
}
}
mLastClickTime = SystemClock.elapsedRealtime();
refreshState();
}
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
state.value = isSmoothDisplayEnabled();
state.label = getTileLabel();
state.icon = ResourceIcon.get(R.drawable.ic_qs_refresh_rate);
if (mBatteryController.isPowerSave()) {
state.state = Tile.STATE_UNAVAILABLE;
state.secondaryLabel = mContext.getString(R.string.battery_detail_switch_title);
} else {
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
state.secondaryLabel = getSecondaryLabel();
}
state.contentDescription = TextUtils.concat(state.label, ", ", state.secondaryLabel);
}
@Override
public int getMetricsCategory() {
return MetricsEvent.QS_CUSTOM;
}
@Override
public Intent getLongClickIntent() {
return new Intent(mHighRefreshRates.size() > 1 ? ACTION_SMOOTH_DISPLAY_SETTINGS
: Settings.ACTION_DISPLAY_SETTINGS);
}
@Override
public CharSequence getTileLabel() {
return mContext.getString(R.string.smooth_display);
}
private String getSecondaryLabel() {
final boolean on = isSmoothDisplayEnabled();
final String status = mContext.getString(
on ? R.string.switch_bar_on : R.string.switch_bar_off);
final int refreshRate = on ? getPeakRefreshRate() : DEFAULT_REFRESH_RATE;
return String.format("%s (%d Hz)", status, refreshRate);
}
private List<Integer> getHighRefreshRates() {
return Arrays.stream(mContext.getDisplay().getSupportedModes())
.mapToInt(m -> Math.round(m.getRefreshRate()))
.filter(r -> r > DEFAULT_REFRESH_RATE)
.sorted().boxed().collect(Collectors.toList());
}
private int roundToNearestHighRefreshRate(int refreshRate) {
if (mHighRefreshRates.contains(refreshRate)) return refreshRate;
int findRefreshRate = mLeastHighRefreshRate;
for (Integer highRefreshRate : mHighRefreshRates) {
if (highRefreshRate > refreshRate) break;
findRefreshRate = highRefreshRate;
}
return findRefreshRate;
}
private int getDefaultPeakRefreshRate() {
return mContext.getResources().getInteger(
com.android.internal.R.integer.config_defaultPeakRefreshRate);
}
private int getPeakRefreshRate() {
final int peakRefreshRate = Math.round(Settings.System.getFloat(
mContext.getContentResolver(),
Settings.System.PEAK_REFRESH_RATE, getDefaultPeakRefreshRate()));
if (peakRefreshRate < DEFAULT_REFRESH_RATE) {
return mMaxRefreshRate;
} else if (peakRefreshRate < mLeastHighRefreshRate) {
return DEFAULT_REFRESH_RATE;
}
return roundToNearestHighRefreshRate(peakRefreshRate);
}
private void setPeakRefreshRate(int refreshRate) {
Settings.System.putFloat(mContext.getContentResolver(),
Settings.System.PEAK_REFRESH_RATE, (float) refreshRate);
}
private int getSmoothRefreshRate() {
final int smoothRefreshRate = Math.round(Settings.System.getFloat(
mContext.getContentResolver(),
Settings.System.SMOOTH_REFRESH_RATE, (float) getPeakRefreshRate()));
if (smoothRefreshRate == DEFAULT_REFRESH_RATE) {
return Math.max(mLeastHighRefreshRate, getDefaultPeakRefreshRate());
}
return roundToNearestHighRefreshRate(smoothRefreshRate);
}
private boolean setSmoothRefreshRate(int refreshRate) {
setPeakRefreshRate(refreshRate);
Settings.System.putFloat(mContext.getContentResolver(),
Settings.System.SMOOTH_REFRESH_RATE, (float) refreshRate);
return true;
}
private boolean isSmoothDisplayEnabled() {
return getPeakRefreshRate() >= mLeastHighRefreshRate;
}
}