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

Unverified Commit 5ebf0d94 authored by Michael Bestas's avatar Michael Bestas Committed by Michael Bestas
Browse files

Bring back font and icon shape customization

Author: Michael Bestas <mkbestas@gmail.com>
Date:   Thu Jan 20 00:03:45 2022 +0200

    Bring back font and icon shape customization

    * AOSP removed those settings from ThemePicker,
      we can add them back here since the underlying mechanism
      still exists and works perfectly.

    Change-Id: I84449f0b3c95654b0caa5c7654a86f397da0ced4

Author: Michael Bestas <mkbestas@lineageos.org>
Date:   Mon Mar 14 17:02:16 2022 +0200

    OverlayCategoryPreferenceController: Change the way we set overlays

    Before this patch, we were just setting overlays on the fly
    without saving them anywhere persistent, which ended up
    removing font and icon shape customizations on wallpaper change
    or device reboot.

    Modify Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES
    instead and SystemUI will apply the overlays automatically.

    Test: Change wallpaper, observe font and icon shape customizations
          are still applied.
    Change-Id: Idc50dc1cea1cdb239a0949425f14277c6d1ce332

Change-Id: Ib2727d28b4f570c5664d731f9bcd37433084f659
parent 81dbb0b0
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -36,6 +36,11 @@
    <!-- [CHAR LIMIT=NONE] Device Info screen. Okay we get it, stop pressing, you already have it on -->
    <string name="show_dev_already_cm">No need, you have already enabled development settings.</string>

    <!-- [CHAR_LIMIT=NONE] Display Settings: Title of the setting which enables overlays to customize headline and body fonts. -->
    <string name="theme_customization_font_title">Font</string>
    <!-- [CHAR_LIMIT=NONE] Display Settings: Title of the setting which enables overlays to customize the adaptive icon shape (e.g. launcher icons). -->
    <string name="theme_customization_icon_shape_title">Icon shape</string>

    <!-- Display settings screen, peak refresh rate settings summary [CHAR LIMIT=NONE] -->
    <string name="peak_refresh_rate_summary_custom">Automatically raises the refresh rate from 60 to %1$d Hz for some content. Increases battery usage.</string>

+8 −0
Original line number Diff line number Diff line
@@ -68,6 +68,14 @@
            settings:controller="com.android.settings.display.DarkUIPreferenceController"
            settings:keywords="@string/keywords_dark_ui_mode"/>

        <ListPreference
            android:key="android.theme.customization.font"
            android:title="@string/theme_customization_font_title"/>

        <ListPreference
            android:key="android.theme.customization.adaptive_icon_shape"
            android:title="@string/theme_customization_icon_shape_title"/>

        <Preference
            android:fragment="com.android.settings.accessibility.TextReadingPreferenceFragment"
            android:key="text_reading_options"
+5 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.display.BrightnessLevelPreferenceController;
import com.android.settings.display.CameraGesturePreferenceController;
import com.android.settings.display.LiftToWakePreferenceController;
import com.android.settings.display.OverlayCategoryPreferenceController;
import com.android.settings.display.ScreenSaverPreferenceController;
import com.android.settings.display.ShowOperatorNamePreferenceController;
import com.android.settings.display.TapToWakePreferenceController;
@@ -82,6 +83,10 @@ public class DisplaySettings extends DashboardFragment {
        controllers.add(new ShowOperatorNamePreferenceController(context));
        controllers.add(new ThemePreferenceController(context));
        controllers.add(new BrightnessLevelPreferenceController(context, lifecycle));
        controllers.add(new OverlayCategoryPreferenceController(context,
                "android.theme.customization.font"));
        controllers.add(new OverlayCategoryPreferenceController(context,
                "android.theme.customization.adaptive_icon_shape"));
        return controllers;
    }

+220 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source 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 com.android.settings.display;

import static android.os.UserHandle.USER_SYSTEM;

import android.content.Context;
import android.content.om.IOverlayManager;
import android.content.om.OverlayInfo;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;

import androidx.annotation.VisibleForTesting;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;

import com.android.settings.R;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settingslib.core.AbstractPreferenceController;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

/**
 * Preference controller to allow users to choose an overlay from a list for a given category.
 * The chosen overlay is enabled exclusively within its category. A default option is also
 * exposed that disables all overlays in the given category.
 */
public class OverlayCategoryPreferenceController extends AbstractPreferenceController
        implements Preference.OnPreferenceChangeListener, PreferenceControllerMixin {
    private static final String TAG = "OverlayCategoryPC";
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
    @VisibleForTesting
    static final String PACKAGE_DEVICE_DEFAULT = "package_device_default";
    private static final String OVERLAY_TARGET_PACKAGE = "android";
    private static final Comparator<OverlayInfo> OVERLAY_INFO_COMPARATOR =
            Comparator.comparingInt(a -> a.priority);
    private final IOverlayManager mOverlayManager;
    private final boolean mAvailable;
    private final String mCategory;
    private final PackageManager mPackageManager;

    private ListPreference mPreference;

    @VisibleForTesting
    OverlayCategoryPreferenceController(Context context, PackageManager packageManager,
            IOverlayManager overlayManager, String category) {
        super(context);
        mOverlayManager = overlayManager;
        mPackageManager = packageManager;
        mCategory = category;
        mAvailable = overlayManager != null && !getOverlayInfos().isEmpty();
    }

    public OverlayCategoryPreferenceController(Context context, String category) {
        this(context, context.getPackageManager(), IOverlayManager.Stub
                .asInterface(ServiceManager.getService(Context.OVERLAY_SERVICE)), category);
    }

    @Override
    public boolean isAvailable() {
        return mAvailable;
    }

    @Override
    public String getPreferenceKey() {
        return mCategory;
    }

    @Override
    public void displayPreference(PreferenceScreen screen) {
        super.displayPreference(screen);
        setPreference(screen.findPreference(getPreferenceKey()));
    }

    @VisibleForTesting
    void setPreference(ListPreference preference) {
        mPreference = preference;
    }

    @Override
    public boolean onPreferenceChange(Preference preference, Object newValue) {
        return setOverlay((String) newValue);
    }

    private boolean setOverlay(String packageName) {
        final String currentPackageName = getOverlayInfos().stream()
                .filter(info -> info.isEnabled())
                .map(info -> info.packageName)
                .findFirst()
                .orElse(null);

        if (PACKAGE_DEVICE_DEFAULT.equals(packageName) && TextUtils.isEmpty(currentPackageName)
                || TextUtils.equals(packageName, currentPackageName)) {
            // Already set.
            return true;
        }

        new AsyncTask<Void, Void, Boolean>() {
            @Override
            protected Boolean doInBackground(Void... params) {
                try {
                    String overlayPackageJson = Settings.Secure.getStringForUser(
                            mContext.getContentResolver(),
                            Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
                            UserHandle.USER_CURRENT);
                    JSONObject jsonObject = (overlayPackageJson == null) ? new JSONObject()
                            : new JSONObject(overlayPackageJson);
                    if (jsonObject.has(mCategory)) {
                        jsonObject.remove(mCategory);
                    }

                    if (!PACKAGE_DEVICE_DEFAULT.equals(packageName)) {
                        jsonObject.put(mCategory, packageName);
                    }

                    if (DEBUG) {
                        Log.d(TAG, "Updating theme setting from "
                                + overlayPackageJson + " to " + jsonObject.toString());
                    }
                    Settings.Secure.putString(mContext.getContentResolver(),
                            Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
                            jsonObject.toString());
                    return true;
                } catch (JSONException e) {
                    Log.w(TAG, "Failed to parse THEME_CUSTOMIZATION_OVERLAY_PACKAGES.", e);
                    return false;
                }
            }

            @Override
            protected void onPostExecute(Boolean success) {
                updateState(mPreference);
                if (!success) {
                    Toast.makeText(
                            mContext, R.string.overlay_toast_failed_to_apply, Toast.LENGTH_LONG)
                            .show();
                }
            }
        }.execute();

        return true; // Assume success; toast on failure.
    }

    @Override
    public void updateState(Preference preference) {
        final List<String> pkgs = new ArrayList<>();
        final List<String> labels = new ArrayList<>();

        String selectedPkg = PACKAGE_DEVICE_DEFAULT;
        String selectedLabel = mContext.getString(R.string.overlay_option_device_default);

        // Add the default package / label before all of the overlays
        pkgs.add(selectedPkg);
        labels.add(selectedLabel);

        for (OverlayInfo overlayInfo : getOverlayInfos()) {
            pkgs.add(overlayInfo.packageName);
            try {
                labels.add(mPackageManager.getApplicationInfo(overlayInfo.packageName, 0)
                        .loadLabel(mPackageManager).toString());
            } catch (PackageManager.NameNotFoundException e) {
                labels.add(overlayInfo.packageName);
            }
            if (overlayInfo.isEnabled()) {
                selectedPkg = pkgs.get(pkgs.size() - 1);
                selectedLabel = labels.get(labels.size() - 1);
            }
        }

        mPreference.setEntries(labels.toArray(new String[labels.size()]));
        mPreference.setEntryValues(pkgs.toArray(new String[pkgs.size()]));
        mPreference.setValue(selectedPkg);
        mPreference.setSummary(selectedLabel);
    }

    private List<OverlayInfo> getOverlayInfos() {
        final List<OverlayInfo> filteredInfos = new ArrayList<>();
        try {
            List<OverlayInfo> overlayInfos = mOverlayManager
                    .getOverlayInfosForTarget(OVERLAY_TARGET_PACKAGE, USER_SYSTEM);
            for (OverlayInfo overlayInfo : overlayInfos) {
                if (mCategory.equals(overlayInfo.category)) {
                    filteredInfos.add(overlayInfo);
                }
            }
        } catch (RemoteException re) {
            throw re.rethrowFromSystemServer();
        }
        filteredInfos.sort(OVERLAY_INFO_COMPARATOR);
        return filteredInfos;
    }

}