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

Commit 33d18cc3 authored by Santiago Etchebehere's avatar Santiago Etchebehere
Browse files

[ThemePicker 5/N] Create a ThemeProvider

Create a Provider that can read the themes available in the
system from a stub apk.
For now we only read Font and Color overlays (the rest
coming in a follow up CL).

Bug: 120559294

Change-Id: I78f1a31073ff219befc7b0d37227afb38c746380
parent 8f2672a7
Loading
Loading
Loading
Loading
+20 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--

     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.
-->
<resources>
    <string name="themes_stub_package" translatable="false"/>
</resources>
 No newline at end of file
+51 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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.customization.model;

import java.util.List;

/**
 * Interface for a class that handles a "Customization" (eg, "Themes", "Clockfaces", etc)
 * @param <T> the type of {@link CustomizationOption} that this Manager class provides.
 */
public interface CustomizationManager<T extends CustomizationOption> {

    /**
     * Listener interface for fetching CustomizationOptions
     */
    interface OptionsFetchedListener<T extends CustomizationOption> {
        /**
         * Called when the options have been retrieved.
         */
        void onOptionsLoaded(List<T> options);
    }

    /**
     * Returns whether this customization is available in the system.
     */
    boolean isAvailable();

    /**
     * Applies the given option into the system.
     */
    void apply(T option);

    /**
     * Loads the available options for the type of Customization managed by this class, calling the
     * given callback when done.
     */
    void fetchOptions(OptionsFetchedListener<T> callback);
}
+3 −3
Original line number Diff line number Diff line
@@ -17,13 +17,13 @@ package com.android.customization.model;

import android.view.View;

import com.android.wallpaper.R;

import androidx.annotation.LayoutRes;

import com.android.wallpaper.R;


/**
 * Represents an option of customization (eg, a Theme, a Clock face, a Grid size)
 * Represents an option of customization (eg, a ThemeBundle, a Clock face, a Grid size)
 */
public interface CustomizationOption {

+163 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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.customization.model.theme;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
import android.graphics.Typeface;
import android.text.TextUtils;
import android.util.Log;

import com.android.customization.model.CustomizationManager.OptionsFetchedListener;
import com.android.customization.model.theme.ThemeBundle.Builder;
import com.android.wallpaper.R;

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

/**
 * Default implementation of {@link ThemeBundleProvider} that reads Themes' overlays from a stub APK.
 */
public class DefaultThemeProvider implements ThemeBundleProvider {

    private static final String TAG = "DefaultThemeProvider";

    private static final String THEMES_ARRAY = "themes";
    private static final String TITLE_PREFIX = "theme_title_";
    private static final String FONT_PREFIX = "theme_overlay_font_";
    private static final String COLOR_PREFIX = "theme_overlay_color_";

    private static final String ACCENT_COLOR_LIGHT_NAME = "accent_device_default_light";
    private static final String ACCENT_COLOR_DARK_NAME = "accent_device_default_dark";
    private static final String CONFIG_BODY_FONT_FAMILY = "config_bodyFontFamily";
    private static final String CONFIG_HEADLINE_FONT_FAMILY = "config_headlineFontFamily";

    private final Context mContext;
    private final String mStubPackageName;
    private Resources mStubApkResources;
    private List<ThemeBundle> mThemes;

    public DefaultThemeProvider(Context context) {
        mContext = context;
        mStubPackageName = mContext.getString(R.string.themes_stub_package);
        init();
    }

    private void init() {
        mStubApkResources = null;
        try {
            PackageManager pm = mContext.getPackageManager();
            ApplicationInfo stubAppInfo = pm.getApplicationInfo(
                    mStubPackageName, PackageManager.GET_META_DATA);
            if (stubAppInfo != null) {
                mStubApkResources = pm.getResourcesForApplication(stubAppInfo);
            }
        } catch (NameNotFoundException e) {
            Log.w(TAG, "Themes stub APK not found.");
        }
    }

    @Override
    public boolean isAvailable() {
        return mStubApkResources != null;
    }

    @Override
    public void fetch(OptionsFetchedListener<ThemeBundle> callback, boolean reload) {
        if (mThemes == null || reload) {
            mThemes = new ArrayList<>();
            readThemesFromStub();
        }

        if(callback != null) {
            callback.onOptionsLoaded(mThemes);
        }
    }

    private void readThemesFromStub() {
        int themesListResId = mStubApkResources.getIdentifier(THEMES_ARRAY, "array",
                mStubPackageName);
        String[] themeNames = mStubApkResources.getStringArray(themesListResId);

        for (String themeName : themeNames) {
            ThemeBundle.Builder builder = new Builder();
            try {
                builder.setTitle(mStubApkResources.getString(
                        mStubApkResources.getIdentifier(TITLE_PREFIX + themeName,
                                "string", mStubPackageName)));

                String fontOverlayPackage = getOverlayPackage(FONT_PREFIX, themeName);

                if (!TextUtils.isEmpty(fontOverlayPackage)) {
                    builder.setFontOverlayPackage(fontOverlayPackage)
                            .setBodyFontFamily(loadTypeface(CONFIG_BODY_FONT_FAMILY,
                                    fontOverlayPackage))
                            .setHeadlineFontFamily(loadTypeface(CONFIG_HEADLINE_FONT_FAMILY,
                                    fontOverlayPackage));
                }

                String colorOverlayPackage = getOverlayPackage(COLOR_PREFIX, themeName);

                if (!TextUtils.isEmpty(colorOverlayPackage)) {
                    builder.setColorPackage(colorOverlayPackage)
                            .setColorAccentLight(loadColor(ACCENT_COLOR_LIGHT_NAME,
                                    colorOverlayPackage))
                            .setColorAccentDark(loadColor(ACCENT_COLOR_DARK_NAME,
                                    colorOverlayPackage));
                }

                //TODO (santie) read the other overlays

                mThemes.add(builder.build());
            } catch (NameNotFoundException | NotFoundException e) {
                Log.w(TAG, String.format("Couldn't load part of theme %s, will skip it", themeName),
                        e);
            }
        }
    }

    private String getOverlayPackage(String prefix, String themeName) {
        int overlayPackageResId = mStubApkResources.getIdentifier(prefix + themeName,
                "string", mStubPackageName);
        return mStubApkResources.getString(overlayPackageResId);
    }

    private Typeface loadTypeface(String configName, String fontOverlayPackage)
            throws NameNotFoundException, NotFoundException {

        // TODO(santie): check for font being present in system

        Resources overlayRes = mContext.getPackageManager()
                .getResourcesForApplication(fontOverlayPackage);

        String fontFamily = overlayRes.getString(overlayRes.getIdentifier(configName,
                "string", fontOverlayPackage));
        return Typeface.create(fontFamily, Typeface.NORMAL);
    }

    private int loadColor(String colorName, String colorPackage)
            throws NameNotFoundException, NotFoundException {

        Resources overlayRes = mContext.getPackageManager()
                .getResourcesForApplication(colorPackage);
        return overlayRes.getColor(overlayRes.getIdentifier(colorName, "color", colorPackage),
                null);
    }
}
+149 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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.customization.model.theme;

import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.view.View;

import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;

import com.android.customization.model.CustomizationOption;

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

/**
 * Represents a Theme component available in the system as a "persona" bundle.
 * Note that in this context a Theme is not related to Android's Styles, but it's rather an
 * abstraction representing a series of overlays to be applied to the system.
 */
public class ThemeBundle implements CustomizationOption {

    private final String mTitle;
    private final PreviewInfo mPreviewInfo;

    private ThemeBundle(String title, PreviewInfo previewInfo) {
        mTitle = title;
        mPreviewInfo = previewInfo;
    }

    @Override
    public String getTitle() {
        return mTitle;
    }

    @Override
    public void bindThumbnailTile(View view) {
        //TODO(santie): implement
    }

    @Override
    public boolean isCurrentlySet() {
        return false;
    }

    @Override
    public int getLayoutResId() {
        return 0; //TODO(santie)
    }

    public PreviewInfo getPreviewInfo() {
        return mPreviewInfo;
    }

    public static class PreviewInfo {
        public final Typeface bodyFontFamily;
        public final Typeface headlineFontFamily;
        @ColorInt public final int colorAccentLight;
        @ColorInt public final int colorAccentDark;
        public final List<Drawable> icons;
        public final String shapePath;
        @DrawableRes public final int wallpaperDrawableRes;

        public PreviewInfo(Typeface bodyFontFamily, Typeface headlineFontFamily,
                int colorAccentLight,
                int colorAccentDark, List<Drawable> icons, String shapePath,
                int wallpaperDrawableRes) {
            this.bodyFontFamily = bodyFontFamily;
            this.headlineFontFamily = headlineFontFamily;
            this.colorAccentLight = colorAccentLight;
            this.colorAccentDark = colorAccentDark;
            this.icons = icons;
            this.shapePath = shapePath;
            this.wallpaperDrawableRes = wallpaperDrawableRes;
        }
    }

    public static class Builder {
        private String mTitle;
        private Typeface mBodyFontFamily;
        private Typeface mHeadlineFontFamily;
        @ColorInt private int mColorAccentLight;
        @ColorInt private int mColorAccentDark;
        private List<Drawable> mIcons = new ArrayList<>();
        private String mShapePath;
        @DrawableRes private int mWallpaperDrawableResId;

        private String mFontOverlayPackage;
        private String mColorsPackage;
        private List<String> mIconPackages = new ArrayList<>();
        private String mShapePackage;

        public ThemeBundle build() {
            return new ThemeBundle(mTitle,
                    new PreviewInfo(mBodyFontFamily, mHeadlineFontFamily, mColorAccentLight,
                            mColorAccentDark, mIcons, mShapePath, mWallpaperDrawableResId));
        }

        public Builder setTitle(String title) {
            mTitle = title;
            return this;
        }

        public Builder setFontOverlayPackage(String packageName) {
            mFontOverlayPackage = packageName;
            return this;
        }

        public Builder setBodyFontFamily(@Nullable Typeface bodyFontFamily) {
            mBodyFontFamily = bodyFontFamily;
            return this;
        }

        public Builder setHeadlineFontFamily(@Nullable Typeface headlineFontFamily) {
            mHeadlineFontFamily = headlineFontFamily;
            return this;
        }

        public Builder setColorPackage(String packageName) {
            mColorsPackage = packageName;
            return this;
        }

        public Builder setColorAccentLight(@ColorInt int colorAccentLight) {
            mColorAccentLight = colorAccentLight;
            return this;
        }

        public Builder setColorAccentDark(@ColorInt int colorAccentDark) {
            mColorAccentDark = colorAccentDark;
            return this;
        }
    }
}
Loading