spes: parts: import Refresh Rate tile

This commit is contained in:
shifty-d
2024-10-18 20:11:15 +03:00
committed by spesmynuts
parent a4484fbe22
commit cb4204121c
23 changed files with 1359 additions and 0 deletions

View File

@@ -573,3 +573,10 @@ $(call inherit-product, vendor/xiaomi/miuicamera/config.mk)
# Remove unnecessary system apps (e.g., AudioFX)
PRODUCT_PACKAGES += \
RemovePackages
# Refreshrate
PRODUCT_PACKAGES += \
RefreshRateParts
PRODUCT_COPY_FILES += \
$(LOCAL_PATH)/refreshrate/privapp-permissions-refresh-rate-parts.xml:$(TARGET_COPY_OUT_SYSTEM_EXT)/etc/permissions/privapp-permissions-refresh-rate-parts.xml

22
refreshrate/Android.bp Normal file
View File

@@ -0,0 +1,22 @@
android_app {
name: "RefreshRateParts",
srcs: ["src/**/*.java"],
certificate: "platform",
platform_apis: true,
system_ext_specific: true,
privileged: true,
static_libs: [
"androidx.core_core",
"androidx.preference_preference",
"org.lineageos.settings.resources",
"SettingsLib",
],
optimize: {
proguard_flags_files: ["proguard.flags"],
},
}

View File

@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="co.aospa.settings"
android:versionCode="4"
android:versionName="4.0"
android:sharedUserId="android.uid.system">
<uses-permission android:name="android.permission.DEVICE_POWER" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
<uses-permission android:name="android.permission.GET_TASKS" />
<application
android:label="@string/device_settings_app_name"
android:persistent="true"
android:directBootAware="true">
<!-- TODO after package rename in U:
android:defaultToDeviceProtectedStorage="true" -->
<receiver
android:name=".BootCompletedReceiver"
android:exported="true">
<intent-filter android:priority="1000">
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
</intent-filter>
</receiver>
<activity
android:name=".refreshrate.RefreshActivity"
android:label="@string/refresh_title"
android:exported="true"
android:theme="@style/Theme.SubSettingsBase">
<intent-filter>
<action android:name="com.android.settings.action.IA_SETTINGS" />
</intent-filter>
<meta-data
android:name="com.android.settings.category"
android:value="com.android.settings.category.ia.display" />
<meta-data
android:name="com.android.settings.summary"
android:resource="@string/refresh_summary" />
</activity>
<service
android:name=".refreshrate.RefreshService"
android:permission="RefreshService">
</service>
<service
android:name=".refreshrate.RefreshTileService"
android:exported="true"
android:icon="@drawable/ic_refresh_tile"
android:label="@string/refresh_rate_tile_title"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE"/>
</intent-filter>
</service>
<activity
android:name=".TileEntryActivity"
android:label="@string/device_settings_app_name"
android:exported="true"
android:theme="@style/Theme.SubSettingsBase">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017-2019 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.
-->
<permissions>
<privapp-permissions package="co.aospa.settings">
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
<permission name="android.permission.INTERACT_ACROSS_USERS"/>
</privapp-permissions>
</permissions>

View File

@@ -0,0 +1,3 @@
-keep class co.aospa.settings.* {
*;
}

View File

@@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?android:attr/colorAccent">
<path
android:fillColor="#ff000000"
android:pathData="M6.55 8c-0.4 0-0.8 0.16-1.1 0.46C5.17 8.75 5 9.14 5 9.56v4.66c0 0.4 0.16 0.8 0.46 1.1 0.29 0.29 0.68 0.45 1.1 0.45H8.1c0.41 0 0.8-0.16 1.1-0.46 0.29-0.29 0.45-0.68 0.45-1.1v-1.55c0-0.41-0.16-0.8-0.45-1.1-0.3-0.29-0.69-0.45-1.1-0.45H6.55V9.55h3.11V8h-3.1Zm0 4.66h1.56v1.56H6.55v-1.56Z"/>
<path
android:fillColor="#ff000000"
android:pathData="M12 8c-0.42 0-0.81 0.16-1.1 0.46-0.3 0.29-0.46 0.68-0.46 1.1v4.66c0 0.4 0.16 0.8 0.45 1.1 0.3 0.29 0.69 0.45 1.1 0.45h1.56c0.4 0 0.8-0.16 1.1-0.46 0.29-0.29 0.45-0.68 0.45-1.1V9.56c0-0.4-0.16-0.8-0.46-1.1C14.35 8.17 13.96 8 13.54 8H12Zm0 1.55h1.55v4.67h-1.56V9.55Z"/>
<path
android:fillColor="#ff000000"
android:pathData="M17 1.01L7 1C5.9 1 5 1.9 5 3v4h2V3h10v18H7v-4H5v4c0 1.1 0.9 2 2 2h10c1.1 0 2-0.9 2-2V3c0-1.1-0.9-1.99-2-1.99Z"/>
</vector>

View File

@@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?android:attr/colorAccent">
<path
android:fillColor="#ff000000"
android:pathData="M17 1.01L7 1C5.9 1 5 1.9 5 3v4h2V3h10v18H7v-4H5v4c0 1.1 0.9 2 2 2h10c1.1 0 2-0.9 2-2V3c0-1.1-0.9-1.99-2-1.99Z"/>
<path
android:fillColor="#ff000000"
android:pathData="M8.1 15.77c0.42 0 0.82-0.16 1.1-0.46 0.3-0.29 0.46-0.68 0.46-1.1V9.56c0-0.4-0.16-0.8-0.45-1.1C8.9 8.17 8.52 8 8.1 8H6.55c-0.4 0-0.8 0.16-1.1 0.46C5.17 8.75 5 9.14 5 9.56v1.55c0 0.41 0.16 0.8 0.46 1.1 0.29 0.29 0.68 0.45 1.1 0.45H8.1v1.56H5v1.55h3.1Zm0-4.66H6.56V9.55h1.56v1.56Z"/>
<path
android:fillColor="#ff000000"
android:pathData="M12 8c-0.42 0-0.81 0.16-1.1 0.46-0.3 0.29-0.46 0.68-0.46 1.1v4.66c0 0.4 0.16 0.8 0.45 1.1 0.3 0.29 0.69 0.45 1.1 0.45h1.56c0.4 0 0.8-0.16 1.1-0.46 0.29-0.29 0.45-0.68 0.45-1.1V9.56c0-0.4-0.16-0.8-0.46-1.1C14.35 8.17 13.96 8 13.54 8H12Zm0 1.55h1.55v4.67h-1.56V9.55Z"/>
</vector>

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"
android:tint="?android:attr/colorAccent">
<path
android:fillColor="#ff000000"
android:pathData="M17 3.01V15l2 2V3.01c0-1.1-0.9-1.99-2-1.99L7 1C6 0.99 5 1.99 5 2.99L7 5V3.01h10Zm4.2 18.19L19 19.01l-2-2-10-10-2-2-2.19-2.19L1.4 4.23 5 7.84v13.17c0 1.1 0.9 2 2 2h10c0.85 0 1.58-0.55 1.87-1.3l0.91 0.91 1.41-1.42ZM17 21.01H7L17 21v-0.99 1Zm-10 0V9.84l10 10.17V21L7 21.01Z"/>
</vector>

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"
android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="#ff000000"
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

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 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.
-->
<androidx.recyclerview.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/refresh_rv_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

View File

@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:paddingBottom="4dp"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingTop="4dp">
<ImageView
android:id="@+id/app_icon"
android:layout_width="@android:dimen/app_icon_size"
android:layout_height="@android:dimen/app_icon_size"
android:layout_marginEnd="8dp"
android:layout_gravity="center_vertical"
android:scaleType="centerInside" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="8dp"
android:layout_gravity="center_vertical"
android:gravity="center_vertical"
android:orientation="vertical">
<TextView
android:id="@+id/app_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:singleLine="true"
android:textAlignment="viewStart"
android:textColor="?android:attr/textColorPrimary"
style="?android:attr/textAppearanceMedium" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<Spinner
android:id="@+id/app_mode"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<ImageView
android:id="@+id/state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:scaleType="centerInside"
android:src="@drawable/ic_refresh_default" />
</LinearLayout>
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 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.
-->
<androidx.recyclerview.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/refresh_rv_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

View File

@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:paddingBottom="4dp"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingTop="4dp">
<ImageView
android:id="@+id/app_icon"
android:layout_width="@android:dimen/app_icon_size"
android:layout_height="@android:dimen/app_icon_size"
android:layout_marginEnd="8dp"
android:layout_gravity="center_vertical"
android:scaleType="centerInside" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="8dp"
android:layout_gravity="center_vertical"
android:gravity="center_vertical"
android:orientation="vertical">
<TextView
android:id="@+id/app_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:singleLine="true"
android:textAlignment="viewStart"
android:textColor="?android:attr/textColorPrimary"
style="?android:attr/textAppearanceMedium" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<Spinner
android:id="@+id/app_mode"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<ImageView
android:id="@+id/state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:scaleType="centerInside"
android:src="@drawable/ic_refresh_default" />
</LinearLayout>
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 20 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.
-->
<resources>
<!-- Refresh Rate -->
<integer-array name="refresh_rate_tile_blocklist">
</integer-array>
</resources>

View File

@@ -0,0 +1,12 @@
<resources>
<!-- Add other string resources here -->
<!-- Add the missing string resources -->
<!-- Refresh Rate -->
<string name="refresh_title">Refresh rate per-app</string>
<string name="refresh_summary">Set a specific refresh rate for each application</string>
<string name="refresh_rate_tile_title">Refresh Rate</string>
<string name="refresh_medium">(60 Hz)</string>
<string name="refresh_high">(90 Hz)</string>
<string name="refresh_default">(60/90 Hz)</string>
</resources>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2015-2016 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<style name="TextAppearance.Medium" parent="@android:style/TextAppearance.Material.Medium" />
</resources>

View File

@@ -0,0 +1,37 @@
package co.aospa.settings;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.util.Log;
import co.aospa.settings.refreshrate.RefreshUtils;
public class BootCompletedReceiver extends BroadcastReceiver {
private static final boolean DEBUG = false;
private static final String TAG = "RefreshRateParts";
@Override
public void onReceive(final Context context, Intent intent) {
Log.i(TAG, "Received intent: " + intent.getAction());
switch (intent.getAction()) {
case Intent.ACTION_LOCKED_BOOT_COMPLETED:
onLockedBootCompleted(context);
break;
case Intent.ACTION_BOOT_COMPLETED:
onBootCompleted(context);
break;
}
}
private static void onLockedBootCompleted(Context context) {
// Services that don't require reading from data.
}
private static void onBootCompleted(Context context) {
// Data is now accessible (user has just unlocked).
RefreshUtils.initialize(context);
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright (C) 2021 Chaldeaprjkt
*
* 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 co.aospa.settings;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.Nullable;
import co.aospa.settings.refreshrate.RefreshActivity;
public class TileEntryActivity extends Activity {
private static final String TAG = "TileEntryActivity";
private static final String REFRESH_TILE = "co.aospa.settings.refreshrate.RefreshTileService";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ComponentName sourceClass = getIntent().getParcelableExtra(Intent.EXTRA_COMPONENT_NAME);
switch (sourceClass.getClassName()) {
case REFRESH_TILE:
openActivitySafely(new Intent(this, RefreshActivity.class));
break;
default:
finish();
break;
}
}
private void openActivitySafely(Intent dest) {
try {
dest.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
finish();
startActivity(dest);
} catch (ActivityNotFoundException e) {
Log.e(TAG, "No activity found for " + dest);
finish();
}
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright (C) 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 co.aospa.settings.refreshrate;
import android.os.Bundle;
import android.view.MenuItem;
import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
import com.android.settingslib.collapsingtoolbar.R;
public class RefreshActivity extends CollapsingToolbarBaseActivity {
private static final String TAG_REFRESH = "refresh";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getFragmentManager().beginTransaction().replace(R.id.content_frame,
new RefreshSettingsFragment(), TAG_REFRESH).commit();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
onBackPressed();
return true;
}
return false;
}
}

View File

@@ -0,0 +1,117 @@
/*
* Copyright (C) 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 co.aospa.settings.refreshrate;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.ActivityTaskManager.RootTaskInfo;
import android.app.IActivityTaskManager;
import android.app.Service;
import android.app.TaskStackListener;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
import android.os.RemoteException;
public class RefreshService extends Service {
private static final String TAG = "RefreshService";
private static final boolean DEBUG = true;
private String mPreviousApp;
private RefreshUtils mRefreshUtils;
private IActivityTaskManager mActivityTaskManager;
private final TaskStackListener mTaskListener = new TaskStackListener() {
@Override
public void onTaskStackChanged() {
try {
final RootTaskInfo info = mActivityTaskManager.getFocusedRootTaskInfo();
if (info == null || info.topActivity == null) {
return;
}
String foregroundApp = info.topActivity.getPackageName();
if (!foregroundApp.equals(mPreviousApp)) {
mRefreshUtils.setRefreshRate(foregroundApp);
mPreviousApp = foregroundApp;
}
} catch (Exception e) {}
}
};
private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
mPreviousApp = "";
mRefreshUtils.setDefaultRefreshRate(context);
}
};
@Override
public void onCreate() {
if (DEBUG) Log.d(TAG, "Creating service");
mRefreshUtils = new RefreshUtils(this);
mRefreshUtils.setDefaultRefreshRate(this);
try {
mActivityTaskManager = ActivityTaskManager.getService();
mActivityTaskManager.registerTaskStackListener(mTaskListener);
} catch (RemoteException e) {
// Do nothing
}
registerReceiver();
super.onCreate();
}
@Override
public void onDestroy() {
if (DEBUG) Log.d(TAG, "Destroying service");
unregisterReceiver();
try {
ActivityTaskManager.getService().unregisterTaskStackListener(mTaskListener);
} catch (RemoteException e) {
// Do nothing
}
mRefreshUtils.setDefaultRefreshRate(this);
super.onDestroy();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (DEBUG) Log.d(TAG, "Starting service");
return START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
private void registerReceiver() {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
this.registerReceiver(mIntentReceiver, filter);
}
private void unregisterReceiver() {
this.unregisterReceiver(mIntentReceiver);
}
}

View File

@@ -0,0 +1,412 @@
/**
* Copyright (C) 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 co.aospa.settings.refreshrate;
import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.SectionIndexer;
import android.widget.Spinner;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceFragment;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.android.settingslib.applications.ApplicationsState;
import co.aospa.settings.R;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class RefreshSettingsFragment extends PreferenceFragment
implements ApplicationsState.Callbacks {
private AllPackagesAdapter mAllPackagesAdapter;
private ApplicationsState mApplicationsState;
private ApplicationsState.Session mSession;
private ActivityFilter mActivityFilter;
private RefreshUtils mRefreshUtils;
private RecyclerView mAppsRecyclerView;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mApplicationsState = ApplicationsState.getInstance(getActivity().getApplication());
mSession = mApplicationsState.newSession(this);
mSession.onResume();
mActivityFilter = new ActivityFilter(getActivity().getPackageManager());
mAllPackagesAdapter = new AllPackagesAdapter(getActivity());
mRefreshUtils = new RefreshUtils(getActivity());
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.refresh_layout, container, false);
}
@Override
public void onViewCreated(final View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mAppsRecyclerView = view.findViewById(R.id.refresh_rv_view);
mAppsRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
mAppsRecyclerView.setAdapter(mAllPackagesAdapter);
}
@Override
public void onResume() {
super.onResume();
rebuild();
}
@Override
public void onDestroy() {
super.onDestroy();
mSession.onPause();
mSession.onDestroy();
}
@Override
public void onPackageListChanged() {
mActivityFilter.updateLauncherInfoList();
rebuild();
}
@Override
public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> entries) {
if (entries != null) {
handleAppEntries(entries);
mAllPackagesAdapter.notifyDataSetChanged();
}
}
@Override
public void onLoadEntriesCompleted() {
rebuild();
}
@Override
public void onAllSizesComputed() {
}
@Override
public void onLauncherInfoChanged() {
}
@Override
public void onPackageIconChanged() {
}
@Override
public void onPackageSizeChanged(String packageName) {
}
@Override
public void onRunningStateChanged(boolean running) {
}
private void handleAppEntries(List<ApplicationsState.AppEntry> entries) {
final ArrayList<String> sections = new ArrayList<String>();
final ArrayList<Integer> positions = new ArrayList<Integer>();
final PackageManager pm = getActivity().getPackageManager();
String lastSectionIndex = null;
int offset = 0;
for (int i = 0; i < entries.size(); i++) {
final ApplicationInfo info = entries.get(i).info;
final String label = (String) info.loadLabel(pm);
final String sectionIndex;
if (!info.enabled) {
sectionIndex = "--"; // XXX
} else if (TextUtils.isEmpty(label)) {
sectionIndex = "";
} else {
sectionIndex = label.substring(0, 1).toUpperCase();
}
if (lastSectionIndex == null ||
!TextUtils.equals(sectionIndex, lastSectionIndex)) {
sections.add(sectionIndex);
positions.add(offset);
lastSectionIndex = sectionIndex;
}
offset++;
}
mAllPackagesAdapter.setEntries(entries, sections, positions);
}
private void rebuild() {
mSession.rebuild(mActivityFilter, ApplicationsState.ALPHA_COMPARATOR);
}
private int getStateDrawable(int state) {
switch (state) {
case RefreshUtils.STATE_MEDIUM:
return R.drawable.ic_refresh_60;
case RefreshUtils.STATE_HIGH:
return R.drawable.ic_refresh_90;
case RefreshUtils.STATE_DEFAULT:
default:
return R.drawable.ic_refresh_default;
}
}
private class ViewHolder extends RecyclerView.ViewHolder {
private TextView title;
private Spinner mode;
private ImageView icon;
private View rootView;
private ImageView stateIcon;
private ViewHolder(View view) {
super(view);
this.title = view.findViewById(R.id.app_name);
this.mode = view.findViewById(R.id.app_mode);
this.icon = view.findViewById(R.id.app_icon);
this.stateIcon = view.findViewById(R.id.state);
this.rootView = view;
view.setTag(this);
}
}
private class ModeAdapter extends BaseAdapter {
private final LayoutInflater inflater;
private final int[] items = {
R.string.refresh_default,
R.string.refresh_medium,
R.string.refresh_high
};
private ModeAdapter(Context context) {
inflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
return items.length;
}
@Override
public Object getItem(int position) {
return items[position];
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView view;
if (convertView != null) {
view = (TextView) convertView;
} else {
view = (TextView) inflater.inflate(android.R.layout.simple_spinner_dropdown_item,
parent, false);
}
view.setText(items[position]);
view.setTextSize(14f);
return view;
}
}
private class AllPackagesAdapter extends RecyclerView.Adapter<ViewHolder>
implements AdapterView.OnItemSelectedListener, SectionIndexer {
private List<ApplicationsState.AppEntry> mEntries = new ArrayList<>();
private String[] mSections;
private int[] mPositions;
public AllPackagesAdapter(Context context) {
mActivityFilter = new ActivityFilter(context.getPackageManager());
}
@Override
public int getItemCount() {
return mEntries.size();
}
@Override
public long getItemId(int position) {
return mEntries.get(position).id;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new ViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.refresh_list_item, parent, false));
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Context context = holder.itemView.getContext();
ApplicationsState.AppEntry entry = mEntries.get(position);
if (entry == null) {
return;
}
holder.mode.setAdapter(new ModeAdapter(context));
holder.mode.setOnItemSelectedListener(this);
holder.title.setText(entry.label);
holder.title.setOnClickListener(v -> holder.mode.performClick());
mApplicationsState.ensureIcon(entry);
holder.icon.setImageDrawable(entry.icon);
int packageState = mRefreshUtils.getStateForPackage(entry.info.packageName);
holder.mode.setSelection(packageState, false);
holder.mode.setTag(entry);
holder.stateIcon.setImageResource(getStateDrawable(packageState));
holder.stateIcon.setOnClickListener(v -> holder.mode.performClick());
}
private void setEntries(List<ApplicationsState.AppEntry> entries,
List<String> sections, List<Integer> positions) {
mEntries = entries;
mSections = sections.toArray(new String[sections.size()]);
mPositions = new int[positions.size()];
for (int i = 0; i < positions.size(); i++) {
mPositions[i] = positions.get(i);
}
notifyDataSetChanged();
}
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
final ApplicationsState.AppEntry entry = (ApplicationsState.AppEntry) parent.getTag();
int currentState = mRefreshUtils.getStateForPackage(entry.info.packageName);
if (currentState != position) {
mRefreshUtils.writePackage(entry.info.packageName, position);
notifyDataSetChanged();
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
@Override
public int getPositionForSection(int section) {
if (section < 0 || section >= mSections.length) {
return -1;
}
return mPositions[section];
}
@Override
public int getSectionForPosition(int position) {
if (position < 0 || position >= getItemCount()) {
return -1;
}
final int index = Arrays.binarySearch(mPositions, position);
/*
* Consider this example: section positions are 0, 3, 5; the supplied
* position is 4. The section corresponding to position 4 starts at
* position 3, so the expected return value is 1. Binary search will not
* find 4 in the array and thus will return -insertPosition-1, i.e. -3.
* To get from that number to the expected value of 1 we need to negate
* and subtract 2.
*/
return index >= 0 ? index : -index - 2;
}
@Override
public Object[] getSections() {
return mSections;
}
}
private class ActivityFilter implements ApplicationsState.AppFilter {
private final PackageManager mPackageManager;
private final List<String> mLauncherResolveInfoList = new ArrayList<String>();
private ActivityFilter(PackageManager packageManager) {
this.mPackageManager = packageManager;
updateLauncherInfoList();
}
public void updateLauncherInfoList() {
Intent i = new Intent(Intent.ACTION_MAIN);
i.addCategory(Intent.CATEGORY_LAUNCHER);
List<ResolveInfo> resolveInfoList = mPackageManager.queryIntentActivities(i, 0);
synchronized (mLauncherResolveInfoList) {
mLauncherResolveInfoList.clear();
for (ResolveInfo ri : resolveInfoList) {
mLauncherResolveInfoList.add(ri.activityInfo.packageName);
}
}
}
@Override
public void init() {
}
@Override
public boolean filterApp(ApplicationsState.AppEntry entry) {
boolean show = !mAllPackagesAdapter.mEntries.contains(entry.info.packageName);
if (show) {
synchronized (mLauncherResolveInfoList) {
show = mLauncherResolveInfoList.contains(entry.info.packageName);
}
}
return show;
}
}
}

View File

@@ -0,0 +1,108 @@
/*
* Copyright (C) 2021 crDroid Android Project
* Copyright (C) 2021 Chaldeaprjkt
*
* 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 co.aospa.settings.refreshrate;
import android.content.Context;
import android.provider.Settings;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
import android.view.Display;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class RefreshTileService extends TileService {
private static final String KEY_MIN_REFRESH_RATE = "min_refresh_rate";
private static final String KEY_PEAK_REFRESH_RATE = "peak_refresh_rate";
private Context context;
private Tile tile;
private final List<Integer> availableRates = new ArrayList<>();
private int activeRateMin;
private int activeRateMax;
@Override
public void onCreate() {
super.onCreate();
context = getApplicationContext();
Display.Mode mode = context.getDisplay().getMode();
Display.Mode[] modes = context.getDisplay().getSupportedModes();
for (Display.Mode m : modes) {
int rate = (int) Math.round(m.getRefreshRate());
if (m.getPhysicalWidth() == mode.getPhysicalWidth() &&
m.getPhysicalHeight() == mode.getPhysicalHeight()) {
availableRates.add(rate);
}
}
syncFromSettings();
}
private int getSettingOf(String key) {
float rate = Settings.System.getFloat(context.getContentResolver(), key, 90);
int active = availableRates.indexOf((int) Math.round(rate));
return Math.max(active, 0);
}
private void syncFromSettings() {
activeRateMin = getSettingOf(KEY_MIN_REFRESH_RATE);
activeRateMax = getSettingOf(KEY_PEAK_REFRESH_RATE);
}
private void cycleRefreshRate() {
if (activeRateMax == 0) {
if(activeRateMin == 0) {
activeRateMin = availableRates.size();
}
activeRateMax = activeRateMin;
float rate = availableRates.get(activeRateMin - 1);
Settings.System.putFloat(context.getContentResolver(), KEY_MIN_REFRESH_RATE, rate);
}
float rate = availableRates.get(activeRateMax - 1);
Settings.System.putFloat(context.getContentResolver(), KEY_PEAK_REFRESH_RATE, rate);
}
private void updateTileView() {
String displayText;
int min = availableRates.get(activeRateMin);
int max = availableRates.get(activeRateMax);
displayText = String.format(Locale.US, min == max ? "%d Hz" : "%d - %d Hz", min, max);
tile.setContentDescription(displayText);
tile.setSubtitle(displayText);
tile.setState(min == max ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE);
tile.updateTile();
}
@Override
public void onStartListening() {
super.onStartListening();
tile = getQsTile();
syncFromSettings();
updateTileView();
}
@Override
public void onClick() {
super.onClick();
cycleRefreshRate();
syncFromSettings();
updateTileView();
}
}

View File

@@ -0,0 +1,168 @@
/*
* Copyright (C) 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 co.aospa.settings.refreshrate;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.UserHandle;
import android.view.Display;
import android.provider.Settings;
import androidx.preference.PreferenceManager;
public final class RefreshUtils {
private static final String REFRESH_CONTROL = "refresh_control";
private static final String REFRESH_SERVICE = "refresh_service";
private static final String KEY_PEAK_REFRESH_RATE = "peak_refresh_rate";
private static final String KEY_MIN_REFRESH_RATE = "min_refresh_rate";
private Context mContext;
protected static final int STATE_DEFAULT = 0;
protected static final int STATE_MEDIUM = 1;
protected static final int STATE_HIGH = 2;
private static final float REFRESH_STATE_DEFAULT = 90f;
private static final float REFRESH_STATE_MEDIUM = 60f;
private static final float REFRESH_STATE_HIGH = 90f;
private static final String REFRESH_MEDIUM = "refresh.medium=";
private static final String REFRESH_HIGH = "refresh.high=";
private static boolean isAppInList = false;
private static float defaultMaxRate;
private static float defaultMinRate;
private SharedPreferences mSharedPrefs;
protected RefreshUtils(Context context) {
mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
mContext = context;
}
public static void initialize(Context context) {
defaultMaxRate = Settings.System.getFloat(context.getContentResolver(), KEY_PEAK_REFRESH_RATE, REFRESH_STATE_DEFAULT);
defaultMinRate = Settings.System.getFloat(context.getContentResolver(), KEY_MIN_REFRESH_RATE, REFRESH_STATE_DEFAULT);
if (isServiceEnabled(context))
startService(context);
else
setDefaultRefreshRate(context);
}
public static void startService(Context context) {
context.startServiceAsUser(new Intent(context, RefreshService.class),
UserHandle.CURRENT);
PreferenceManager.getDefaultSharedPreferences(context).edit().putString(REFRESH_SERVICE, "true").apply();
}
protected static void stopService(Context context) {
context.stopService(new Intent(context, RefreshService.class));
PreferenceManager.getDefaultSharedPreferences(context).edit().putString(REFRESH_SERVICE, "false").apply();
}
protected static boolean isServiceEnabled(Context context) {
return true;
}
private void writeValue(String profiles) {
mSharedPrefs.edit().putString(REFRESH_CONTROL, profiles).apply();
}
private String getValue() {
String value = mSharedPrefs.getString(REFRESH_CONTROL, null);
if (value == null || value.isEmpty()) {
value = REFRESH_MEDIUM + ":" + REFRESH_HIGH;
writeValue(value);
}
return value;
}
protected void writePackage(String packageName, int mode) {
String value = getValue();
value = value.replace(packageName + ",", "");
String[] modes = value.split(":");
String finalString;
switch (mode) {
case STATE_MEDIUM:
modes[0] = modes[0] + packageName + ",";
break;
case STATE_HIGH:
modes[1] = modes[1] + packageName + ",";
break;
}
finalString = modes[0] + ":" + modes[1];
writeValue(finalString);
}
protected int getStateForPackage(String packageName) {
String value = getValue();
String[] modes = value.split(":");
int state = STATE_DEFAULT;
if (modes[0].contains(packageName + ",")) {
state = STATE_MEDIUM;
} else if (modes[1].contains(packageName + ",")) {
state = STATE_HIGH;
}
return state;
}
protected static void setDefaultRefreshRate(Context context) {
Settings.System.putFloat(context.getContentResolver(), KEY_PEAK_REFRESH_RATE, defaultMaxRate);
Settings.System.putFloat(context.getContentResolver(), KEY_MIN_REFRESH_RATE, defaultMinRate);
}
protected void setRefreshRate(String packageName) {
String value = getValue();
String modes[];
if (!isAppInList) {
defaultMaxRate = Settings.System.getFloat(mContext.getContentResolver(), KEY_PEAK_REFRESH_RATE, REFRESH_STATE_DEFAULT);
defaultMinRate = Settings.System.getFloat(mContext.getContentResolver(), KEY_MIN_REFRESH_RATE, REFRESH_STATE_DEFAULT);
}
float minrate = defaultMinRate;
float maxrate = defaultMaxRate;
if (value != null) {
modes = value.split(":");
if (modes[0].contains(packageName + ",")) {
maxrate = REFRESH_STATE_MEDIUM;
if ( minrate > maxrate){
minrate = maxrate;
}
isAppInList = true;
} else if (modes[1].contains(packageName + ",")) {
maxrate = REFRESH_STATE_HIGH;
if ( minrate > maxrate){
minrate = maxrate;
}
isAppInList = true;
} else {
isAppInList = false;
}
}
Settings.System.putFloat(mContext.getContentResolver(), KEY_PEAK_REFRESH_RATE, maxrate);
Settings.System.putFloat(mContext.getContentResolver(), KEY_MIN_REFRESH_RATE, minrate);
}
}