From 3607af54ca48c8a145a97bab0cf5012d45ade555 Mon Sep 17 00:00:00 2001
From: kleidione Freitas <kleidione@gmail.com>
Date: Sun, 6 Mar 2022 11:57:51 -0300
Subject: veux: Import XiaomiParts from sm8250

Credits:
https://github.com/xiaomi-sm8250-devs/android_device_xiaomi_sm8250-common

- Adapte to pixelexperiece
- Drop doze
- Drop fod and pop camera
- Add Clear speaker
- Adapte SEPolicy credis: [Sebastiano Barezzi, Chenyang Zhong]

Co-authored-by: Sebastiano Barezzi <barezzisebastiano@gmail.com>
Co-authored-by: Adithya R <gh0strider.2k18.reborn@gmail.com>
Co-authored-by: kubersharma001 <kubersharma001@gmail.com>
Co-authored-by: TheScarastic <warabhishek@gmail.com>
Co-authored-by: Joey <joey@lineageos.org>
Signed-off-by: kleidione <kleidione@gmail.com>
---
 .../lineageos/settings/BootCompletedReceiver.java  |  37 ++
 .../settings/speaker/ClearSpeakerActivity.java     |  43 ++
 .../settings/speaker/ClearSpeakerFragment.java     | 118 ++++++
 .../settings/thermal/ThermalActivity.java          |  45 +++
 .../lineageos/settings/thermal/ThermalService.java | 102 +++++
 .../settings/thermal/ThermalSettingsFragment.java  | 433 +++++++++++++++++++++
 .../lineageos/settings/thermal/ThermalUtils.java   | 170 ++++++++
 .../org/lineageos/settings/utils/FileUtils.java    | 166 ++++++++
 8 files changed, 1114 insertions(+)
 create mode 100644 parts/src/org/lineageos/settings/BootCompletedReceiver.java
 create mode 100644 parts/src/org/lineageos/settings/speaker/ClearSpeakerActivity.java
 create mode 100644 parts/src/org/lineageos/settings/speaker/ClearSpeakerFragment.java
 create mode 100644 parts/src/org/lineageos/settings/thermal/ThermalActivity.java
 create mode 100644 parts/src/org/lineageos/settings/thermal/ThermalService.java
 create mode 100644 parts/src/org/lineageos/settings/thermal/ThermalSettingsFragment.java
 create mode 100644 parts/src/org/lineageos/settings/thermal/ThermalUtils.java
 create mode 100644 parts/src/org/lineageos/settings/utils/FileUtils.java

(limited to 'parts/src/org')

diff --git a/parts/src/org/lineageos/settings/BootCompletedReceiver.java b/parts/src/org/lineageos/settings/BootCompletedReceiver.java
new file mode 100644
index 0000000..1652bc0
--- /dev/null
+++ b/parts/src/org/lineageos/settings/BootCompletedReceiver.java
@@ -0,0 +1,37 @@
+/*
+ * 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.settings;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import org.lineageos.settings.thermal.ThermalUtils;
+
+public class BootCompletedReceiver extends BroadcastReceiver {
+
+    private static final boolean DEBUG = false;
+    private static final String TAG = "XiaomiParts";
+
+    @Override
+    public void onReceive(final Context context, Intent intent) {
+        if (DEBUG) Log.d(TAG, "Received boot completed intent");
+        ThermalUtils.startService(context);
+    }
+}
diff --git a/parts/src/org/lineageos/settings/speaker/ClearSpeakerActivity.java b/parts/src/org/lineageos/settings/speaker/ClearSpeakerActivity.java
new file mode 100644
index 0000000..5ac5b35
--- /dev/null
+++ b/parts/src/org/lineageos/settings/speaker/ClearSpeakerActivity.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2020 Paranoid Android
+ *
+ * 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.settings.speaker;
+
+import android.os.Bundle;
+import android.view.MenuItem;
+
+import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
+import com.android.settingslib.collapsingtoolbar.R;
+
+public class ClearSpeakerActivity extends CollapsingToolbarBaseActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        getFragmentManager().beginTransaction()
+                .replace(R.id.content_frame,  new ClearSpeakerFragment())
+                .commit();
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        if (item.getItemId() == android.R.id.home) {
+            onBackPressed();
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+}
diff --git a/parts/src/org/lineageos/settings/speaker/ClearSpeakerFragment.java b/parts/src/org/lineageos/settings/speaker/ClearSpeakerFragment.java
new file mode 100644
index 0000000..7f12fd9
--- /dev/null
+++ b/parts/src/org/lineageos/settings/speaker/ClearSpeakerFragment.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2020 Paranoid Android
+ *
+ * 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.settings.speaker;
+
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.media.AudioManager;
+import android.media.AudioAttributes;
+import android.media.MediaPlayer;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragment;
+import androidx.preference.SwitchPreference;
+
+import org.lineageos.settings.R;
+
+import java.io.IOException;
+
+public class ClearSpeakerFragment extends PreferenceFragment implements
+        Preference.OnPreferenceChangeListener {
+
+    private static final String TAG = ClearSpeakerFragment.class.getSimpleName();
+
+    private static final String PREF_CLEAR_SPEAKER = "clear_speaker_pref";
+
+    private AudioManager mAudioManager;
+    private Handler mHandler;
+    private MediaPlayer mMediaPlayer;
+    private SwitchPreference mClearSpeakerPref;
+
+    @Override
+    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+        addPreferencesFromResource(R.xml.clear_speaker_settings);
+
+        mClearSpeakerPref = (SwitchPreference) findPreference(PREF_CLEAR_SPEAKER);
+        mClearSpeakerPref.setOnPreferenceChangeListener(this);
+
+        mHandler = new Handler();
+        mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+    }
+
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        if (preference == mClearSpeakerPref) {
+            boolean value = (Boolean) newValue;
+            if (value) {
+                if (startPlaying()) {
+                    mHandler.removeCallbacksAndMessages(null);
+                    mHandler.postDelayed(() -> {
+                        stopPlaying();
+                    }, 30000);
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public void onStop() {
+        stopPlaying();
+        super.onStop();
+    }
+
+    public boolean startPlaying() {
+        mAudioManager.setParameters("status_earpiece_clean=on");
+        mMediaPlayer = new MediaPlayer();
+        getActivity().setVolumeControlStream(AudioManager.STREAM_MUSIC);
+        mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+        mMediaPlayer.setLooping(true);
+        try {
+            AssetFileDescriptor file = getResources().openRawResourceFd(R.raw.clear_speaker_sound);
+            try {
+                mMediaPlayer.setDataSource(file.getFileDescriptor(), file.getStartOffset(), file.getLength());
+            } finally {
+                file.close();
+            }
+            mClearSpeakerPref.setEnabled(false);
+            mMediaPlayer.setVolume(1.0f, 1.0f);
+            mMediaPlayer.prepare();
+            mMediaPlayer.start();
+        } catch (IOException ioe) {
+            Log.e(TAG, "Failed to play speaker clean sound!", ioe);
+            return false;
+        }
+        return true;
+    }
+
+    public void stopPlaying() {
+        if (mMediaPlayer != null) {
+            mMediaPlayer.stop();
+            mMediaPlayer.reset();
+            mMediaPlayer.release();
+        }
+        mAudioManager.setParameters("status_earpiece_clean=off");
+        mClearSpeakerPref.setEnabled(true);
+        mClearSpeakerPref.setChecked(false);
+    }
+}
diff --git a/parts/src/org/lineageos/settings/thermal/ThermalActivity.java b/parts/src/org/lineageos/settings/thermal/ThermalActivity.java
new file mode 100644
index 0000000..16df382
--- /dev/null
+++ b/parts/src/org/lineageos/settings/thermal/ThermalActivity.java
@@ -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 org.lineageos.settings.thermal;
+
+import android.os.Bundle;
+import android.view.MenuItem;
+
+import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
+import com.android.settingslib.collapsingtoolbar.R;
+
+public class ThermalActivity extends CollapsingToolbarBaseActivity {
+
+    private static final String TAG_THERMAL = "thermal";
+    private static final String THERMAL_SCONFIG = "/sys/class/thermal/thermal_message/sconfig";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        getFragmentManager().beginTransaction().replace(R.id.content_frame,
+                new ThermalSettingsFragment(), TAG_THERMAL).commit();
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        if (item.getItemId() == android.R.id.home) {
+            onBackPressed();
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/parts/src/org/lineageos/settings/thermal/ThermalService.java b/parts/src/org/lineageos/settings/thermal/ThermalService.java
new file mode 100644
index 0000000..cba9044
--- /dev/null
+++ b/parts/src/org/lineageos/settings/thermal/ThermalService.java
@@ -0,0 +1,102 @@
+/*
+ * 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 org.lineageos.settings.thermal;
+
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.ActivityTaskManager.RootTaskInfo;
+import android.app.IActivityTaskManager;
+import android.app.TaskStackListener;
+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.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+public class ThermalService extends Service {
+
+    private static final String TAG = "ThermalService";
+    private static final boolean DEBUG = false;
+
+    private String mPreviousApp;
+    private ThermalUtils mThermalUtils;
+
+    private IActivityTaskManager mActivityTaskManager;
+
+    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mPreviousApp = "";
+            mThermalUtils.setDefaultThermalProfile();
+        }
+    };
+
+    @Override
+    public void onCreate() {
+        if (DEBUG) Log.d(TAG, "Creating service");
+        try {
+            mActivityTaskManager = ActivityTaskManager.getService();
+            mActivityTaskManager.registerTaskStackListener(mTaskListener);
+        } catch (RemoteException e) {
+            // Do nothing
+        }
+        mThermalUtils = new ThermalUtils(this);
+        registerReceiver();
+        super.onCreate();
+    }
+
+    @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 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)) {
+                    mThermalUtils.setThermalProfile(foregroundApp);
+                    mPreviousApp = foregroundApp;
+                }
+            } catch (Exception e) {}
+        }
+    };
+}
diff --git a/parts/src/org/lineageos/settings/thermal/ThermalSettingsFragment.java b/parts/src/org/lineageos/settings/thermal/ThermalSettingsFragment.java
new file mode 100644
index 0000000..6f4e402
--- /dev/null
+++ b/parts/src/org/lineageos/settings/thermal/ThermalSettingsFragment.java
@@ -0,0 +1,433 @@
+/**
+ * Copyright (C) 2020 The LineageOS Project
+ * <p>
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.settings.thermal;
+
+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.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 org.lineageos.settings.R;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ThermalSettingsFragment extends PreferenceFragment
+        implements ApplicationsState.Callbacks {
+
+    private AllPackagesAdapter mAllPackagesAdapter;
+    private ApplicationsState mApplicationsState;
+    private ApplicationsState.Session mSession;
+    private ActivityFilter mActivityFilter;
+    private Map<String, ApplicationsState.AppEntry> mEntryMap =
+            new HashMap<String, ApplicationsState.AppEntry>();
+
+    private RecyclerView mAppsRecyclerView;
+
+    private ThermalUtils mThermalUtils;
+
+    @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());
+
+        mThermalUtils = new ThermalUtils(getActivity());
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        return inflater.inflate(R.layout.thermal_layout, container, false);
+    }
+
+    @Override
+    public void onViewCreated(final View view, @Nullable Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+
+        mAppsRecyclerView = view.findViewById(R.id.thermal_rv_view);
+        mAppsRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
+        mAppsRecyclerView.setAdapter(mAllPackagesAdapter);
+    }
+
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        getActivity().setTitle(getResources().getString(R.string.thermal_title));
+        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);
+        mEntryMap.clear();
+        for (ApplicationsState.AppEntry e : entries) {
+            mEntryMap.put(e.info.packageName, e);
+        }
+    }
+
+    private void rebuild() {
+        mSession.rebuild(mActivityFilter, ApplicationsState.ALPHA_COMPARATOR);
+    }
+
+    private int getStateDrawable(int state) {
+        switch (state) {
+            case ThermalUtils.STATE_BENCHMARK:
+                return R.drawable.ic_thermal_benchmark;
+            case ThermalUtils.STATE_BROWSER:
+                return R.drawable.ic_thermal_browser;
+            case ThermalUtils.STATE_CAMERA:
+                return R.drawable.ic_thermal_camera;
+            case ThermalUtils.STATE_DIALER:
+                return R.drawable.ic_thermal_dialer;
+            case ThermalUtils.STATE_GAMING:
+                return R.drawable.ic_thermal_gaming;
+            case ThermalUtils.STATE_STREAMING:
+                return R.drawable.ic_thermal_streaming;
+            case ThermalUtils.STATE_DEFAULT:
+            default:
+                return R.drawable.ic_thermal_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.thermal_default,
+                R.string.thermal_benchmark,
+                R.string.thermal_browser,
+                R.string.thermal_camera,
+                R.string.thermal_dialer,
+                R.string.thermal_gaming,
+                R.string.thermal_streaming
+        };
+
+        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) {
+            ViewHolder holder = new ViewHolder(LayoutInflater.from(parent.getContext())
+                    .inflate(R.layout.thermal_list_item, parent, false));
+            Context context = holder.itemView.getContext();
+            holder.mode.setAdapter(new ModeAdapter(context));
+            holder.mode.setOnItemSelectedListener(this);
+            return holder;
+        }
+
+        @Override
+        public void onBindViewHolder(ViewHolder holder, int position) {
+            ApplicationsState.AppEntry entry = mEntries.get(position);
+
+            if (entry == null) {
+                return;
+            }
+
+            holder.title.setText(entry.label);
+            holder.title.setOnClickListener(v -> holder.mode.performClick());
+            mApplicationsState.ensureIcon(entry);
+            holder.icon.setImageDrawable(entry.icon);
+            int packageState = mThermalUtils.getStateForPackage(entry.info.packageName);
+            holder.mode.setSelection(packageState, false);
+            holder.mode.setTag(entry);
+            holder.stateIcon.setImageResource(getStateDrawable(packageState));
+        }
+
+        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 = mThermalUtils.getStateForPackage(entry.info.packageName);
+            if (currentState != position) {
+                mThermalUtils.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;
+        }
+    }
+}
diff --git a/parts/src/org/lineageos/settings/thermal/ThermalUtils.java b/parts/src/org/lineageos/settings/thermal/ThermalUtils.java
new file mode 100644
index 0000000..79510ed
--- /dev/null
+++ b/parts/src/org/lineageos/settings/thermal/ThermalUtils.java
@@ -0,0 +1,170 @@
+/*
+ * 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 org.lineageos.settings.thermal;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.UserHandle;
+
+import androidx.preference.PreferenceManager;
+
+import org.lineageos.settings.utils.FileUtils;
+
+public final class ThermalUtils {
+
+    protected static final int STATE_DEFAULT = 0;
+    protected static final int STATE_BENCHMARK = 1;
+    protected static final int STATE_BROWSER = 2;
+    protected static final int STATE_CAMERA = 3;
+    protected static final int STATE_DIALER = 4;
+    protected static final int STATE_GAMING = 5;
+    protected static final int STATE_STREAMING = 6;
+    private static final String THERMAL_CONTROL = "thermal_control";
+    private static final String THERMAL_STATE_DEFAULT = "0";
+    private static final String THERMAL_STATE_BENCHMARK = "10";
+    private static final String THERMAL_STATE_BROWSER = "11";
+    private static final String THERMAL_STATE_CAMERA = "12";
+    private static final String THERMAL_STATE_DIALER = "8";
+    private static final String THERMAL_STATE_GAMING = "9";
+    private static final String THERMAL_STATE_STREAMING = "14";
+
+    private static final String THERMAL_BENCHMARK = "thermal.benchmark=";
+    private static final String THERMAL_BROWSER = "thermal.browser=";
+    private static final String THERMAL_CAMERA = "thermal.camera=";
+    private static final String THERMAL_DIALER = "thermal.dialer=";
+    private static final String THERMAL_GAMING = "thermal.gaming=";
+    private static final String THERMAL_STREAMING = "thermal.streaming=";
+
+    private static final String THERMAL_SCONFIG = "/sys/class/thermal/thermal_message/sconfig";
+
+    private SharedPreferences mSharedPrefs;
+
+    protected ThermalUtils(Context context) {
+        mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
+    }
+
+    public static void startService(Context context) {
+        if (FileUtils.fileExists(THERMAL_SCONFIG)) {
+            context.startServiceAsUser(new Intent(context, ThermalService.class),
+                    UserHandle.CURRENT);
+        }
+    }
+
+    private void writeValue(String profiles) {
+        mSharedPrefs.edit().putString(THERMAL_CONTROL, profiles).apply();
+    }
+
+    private String getValue() {
+        String value = mSharedPrefs.getString(THERMAL_CONTROL, null);
+
+        if (value != null) {
+             String[] modes = value.split(":");
+             if (modes.length < 5) value = null;
+         }
+
+        if (value == null || value.isEmpty()) {
+            value = THERMAL_BENCHMARK + ":" + THERMAL_BROWSER + ":" + THERMAL_CAMERA + ":" +
+                    THERMAL_DIALER + ":" + THERMAL_GAMING + ":" + THERMAL_STREAMING;
+            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_BENCHMARK:
+                modes[0] = modes[0] + packageName + ",";
+                break;
+            case STATE_BROWSER:
+                modes[1] = modes[1] + packageName + ",";
+                break;
+            case STATE_CAMERA:
+                modes[2] = modes[2] + packageName + ",";
+                break;
+            case STATE_DIALER:
+                modes[3] = modes[3] + packageName + ",";
+                break;
+            case STATE_GAMING:
+                modes[4] = modes[4] + packageName + ",";
+                break;
+            case STATE_STREAMING:
+                modes[5] = modes[5] + packageName + ",";
+                break;
+        }
+
+        finalString = modes[0] + ":" + modes[1] + ":" + modes[2] + ":" + modes[3] + ":" +
+                modes[4] + ":" + modes[5];
+
+        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_BENCHMARK;
+        } else if (modes[1].contains(packageName + ",")) {
+            state = STATE_BROWSER;
+        } else if (modes[2].contains(packageName + ",")) {
+            state = STATE_CAMERA;
+        } else if (modes[3].contains(packageName + ",")) {
+            state = STATE_DIALER;
+        } else if (modes[4].contains(packageName + ",")) {
+            state = STATE_GAMING;
+        } else if (modes[5].contains(packageName + ",")) {
+            state = STATE_STREAMING;
+        }
+
+        return state;
+    }
+
+    protected void setDefaultThermalProfile() {
+        FileUtils.writeLine(THERMAL_SCONFIG, THERMAL_STATE_DEFAULT);
+    }
+
+    protected void setThermalProfile(String packageName) {
+        String value = getValue();
+        String modes[];
+        String state = THERMAL_STATE_DEFAULT;
+
+        if (value != null) {
+            modes = value.split(":");
+
+            if (modes[0].contains(packageName + ",")) {
+                state = THERMAL_STATE_BENCHMARK;
+            } else if (modes[1].contains(packageName + ",")) {
+                state = THERMAL_STATE_BROWSER;
+            } else if (modes[2].contains(packageName + ",")) {
+                state = THERMAL_STATE_CAMERA;
+            } else if (modes[3].contains(packageName + ",")) {
+                state = THERMAL_STATE_DIALER;
+            } else if (modes[4].contains(packageName + ",")) {
+                state = THERMAL_STATE_GAMING;
+            } else if (modes[5].contains(packageName + ",")) {
+                state = THERMAL_STATE_STREAMING;
+            }
+        }
+        FileUtils.writeLine(THERMAL_SCONFIG, state);
+    }
+}
diff --git a/parts/src/org/lineageos/settings/utils/FileUtils.java b/parts/src/org/lineageos/settings/utils/FileUtils.java
new file mode 100644
index 0000000..2228ff8
--- /dev/null
+++ b/parts/src/org/lineageos/settings/utils/FileUtils.java
@@ -0,0 +1,166 @@
+/*
+ * 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.settings.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;
+    }
+}
-- 
cgit v1.2.3