/** * 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.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 mEntryMap = new HashMap(); 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 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 entries) { final ArrayList sections = new ArrayList(); final ArrayList positions = new ArrayList(); 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 implements AdapterView.OnItemSelectedListener, SectionIndexer { private List 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 entries, List sections, List 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 mLauncherResolveInfoList = new ArrayList(); private ActivityFilter(PackageManager packageManager) { this.mPackageManager = packageManager; updateLauncherInfoList(); } public void updateLauncherInfoList() { Intent i = new Intent(Intent.ACTION_MAIN); i.addCategory(Intent.CATEGORY_LAUNCHER); List 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; } } }