/* * 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.picker.theme; import android.app.AlertDialog.Builder; import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.TextView; import android.widget.Toast; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; import com.android.customization.model.CustomizationManager.Callback; import com.android.customization.model.theme.DefaultThemeProvider; import com.android.customization.model.theme.OverlayManagerCompat; import com.android.customization.model.theme.ThemeBundle; import com.android.customization.model.theme.ThemeBundleProvider; import com.android.customization.model.theme.ThemeManager; import com.android.customization.model.theme.custom.ColorOptionsProvider; import com.android.customization.model.theme.custom.CustomTheme; import com.android.customization.model.theme.custom.CustomThemeManager; import com.android.customization.model.theme.custom.FontOptionsProvider; import com.android.customization.model.theme.custom.IconOptionsProvider; import com.android.customization.model.theme.custom.ShapeOptionsProvider; import com.android.customization.model.theme.custom.ThemeComponentOption; import com.android.customization.model.theme.custom.ThemeComponentOption.ColorOption; import com.android.customization.model.theme.custom.ThemeComponentOption.FontOption; import com.android.customization.model.theme.custom.ThemeComponentOption.IconOption; import com.android.customization.model.theme.custom.ThemeComponentOption.ShapeOption; import com.android.customization.model.theme.custom.ThemeComponentOptionProvider; import com.android.customization.module.CustomizationInjector; import com.android.customization.module.ThemesUserEventLogger; import com.android.customization.picker.theme.CustomThemeStepFragment.CustomThemeComponentStepHost; import com.android.wallpaper.R; import com.android.wallpaper.module.InjectorProvider; import com.android.wallpaper.picker.AppbarFragment.AppbarFragmentHost; import org.json.JSONException; import java.util.ArrayList; import java.util.List; public class CustomThemeActivity extends FragmentActivity implements AppbarFragmentHost, CustomThemeComponentStepHost { public static final String EXTRA_THEME_ID = "CustomThemeActivity.ThemeId"; public static final String EXTRA_THEME_TITLE = "CustomThemeActivity.ThemeTitle"; public static final String EXTRA_THEME_PACKAGES = "CustomThemeActivity.ThemePackages"; public static final int REQUEST_CODE_CUSTOM_THEME = 1; public static final int RESULT_THEME_DELETED = 10; public static final int RESULT_THEME_APPLIED = 20; private static final String TAG = "CustomThemeActivity"; private static final String KEY_STATE_CURRENT_STEP = "CustomThemeActivity.currentStep"; private ThemesUserEventLogger mUserEventLogger; private List> mSteps; private int mCurrentStep; private CustomThemeManager mCustomThemeManager; private ThemeManager mThemeManager; private TextView mNextButton; private TextView mPreviousButton; @Override protected void onCreate(Bundle savedInstanceState) { CustomizationInjector injector = (CustomizationInjector) InjectorProvider.getInjector(); mUserEventLogger = (ThemesUserEventLogger) injector.getUserEventLogger(this); ThemeBundleProvider themeProvider = new DefaultThemeProvider(this, injector.getCustomizationPreferences(this)); Intent intent = getIntent(); CustomTheme customTheme = null; if (intent != null && intent.hasExtra(EXTRA_THEME_PACKAGES) && intent.hasExtra(EXTRA_THEME_TITLE) && intent.hasExtra(EXTRA_THEME_ID)) { try { CustomTheme.Builder themeBuilder = themeProvider.parseCustomTheme( intent.getStringExtra(EXTRA_THEME_PACKAGES)); if (themeBuilder != null) { themeBuilder.setId(intent.getStringExtra(EXTRA_THEME_ID)); themeBuilder.setTitle(intent.getStringExtra(EXTRA_THEME_TITLE)); customTheme = themeBuilder.build(this); } } catch (JSONException e) { Log.w(TAG, "Couldn't parse provided custom theme, will override it"); } } mThemeManager = injector.getThemeManager( new DefaultThemeProvider(this, injector.getCustomizationPreferences(this)), this, new OverlayManagerCompat(this), mUserEventLogger); mThemeManager.fetchOptions(null, false); mCustomThemeManager = CustomThemeManager.create(customTheme, mThemeManager); if (savedInstanceState != null) { mCustomThemeManager.readCustomTheme(themeProvider, savedInstanceState); } int currentStep = 0; if (savedInstanceState != null) { currentStep = savedInstanceState.getInt(KEY_STATE_CURRENT_STEP); } initSteps(currentStep); super.onCreate(savedInstanceState); setContentView(R.layout.activity_custom_theme); mNextButton = findViewById(R.id.next_button); mNextButton.setOnClickListener(view -> onNextOrApply()); mPreviousButton = findViewById(R.id.previous_button); mPreviousButton.setOnClickListener(view -> onBackPressed()); FragmentManager fm = getSupportFragmentManager(); Fragment fragment = fm.findFragmentById(R.id.fragment_container); if (fragment == null) { // Navigate to the first step navigateToStep(0); } } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt(KEY_STATE_CURRENT_STEP, mCurrentStep); if (mCustomThemeManager != null) { mCustomThemeManager.saveCustomTheme(this, outState); } } private void navigateToStep(int i) { FragmentManager fragmentManager = getSupportFragmentManager(); ComponentStep step = mSteps.get(i); Fragment fragment = step.getFragment(mCustomThemeManager.getOriginalTheme().getTitle()); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.replace(R.id.fragment_container, fragment); // Don't add step 0 to the back stack so that going back from it just finishes the Activity if (i > 0) { fragmentTransaction.addToBackStack("Step " + i); } fragmentTransaction.commit(); fragmentManager.executePendingTransactions(); updateNavigationButtonLabels(); } private void initSteps(int currentStep) { mSteps = new ArrayList<>(); OverlayManagerCompat manager = new OverlayManagerCompat(this); mSteps.add(new FontStep(new FontOptionsProvider(this, manager), 0)); mSteps.add(new IconStep(new IconOptionsProvider(this, manager), 1)); mSteps.add(new ColorStep(new ColorOptionsProvider(this, manager, mCustomThemeManager), 2)); mSteps.add(new ShapeStep(new ShapeOptionsProvider(this, manager), 3)); mSteps.add(new NameStep(4)); mCurrentStep = currentStep; } private void onNextOrApply() { CustomThemeStepFragment stepFragment = getCurrentStepFragment(); if (stepFragment instanceof CustomThemeComponentFragment) { CustomThemeComponentFragment fragment = (CustomThemeComponentFragment) stepFragment; mCustomThemeManager.apply(fragment.getSelectedOption(), new Callback() { @Override public void onSuccess() { navigateToStep(mCurrentStep + 1); } @Override public void onError(@Nullable Throwable throwable) { Log.w(TAG, "Error applying custom theme component", throwable); Toast.makeText(CustomThemeActivity.this, R.string.apply_theme_error_msg, Toast.LENGTH_LONG).show(); } }); } else if (stepFragment instanceof CustomThemeNameFragment) { CustomThemeNameFragment fragment = (CustomThemeNameFragment) stepFragment; CustomTheme originalTheme = mCustomThemeManager.getOriginalTheme(); // We're on the last step, apply theme and leave CustomTheme themeToApply = mCustomThemeManager.buildPartialCustomTheme(this, originalTheme.getId(), fragment.getThemeName()); // If the current theme is equal to the original theme being edited, then // don't search for an equivalent, let the user apply the same one by keeping // it null. ThemeBundle equivalent = (originalTheme.isEquivalent(themeToApply)) ? null : mThemeManager.findThemeByPackages(themeToApply); if (equivalent != null) { Builder builder = new Builder(CustomThemeActivity.this); builder.setTitle(getString(R.string.use_style_instead_title, equivalent.getTitle())) .setMessage(getString(R.string.use_style_instead_body, equivalent.getTitle())) .setPositiveButton(getString(R.string.use_style_button, equivalent.getTitle()), (dialogInterface, i) -> applyTheme(equivalent)) .setNegativeButton(R.string.no_thanks, null) .create() .show(); } else { applyTheme(themeToApply); } } else { throw new IllegalStateException("Unknown CustomThemeStepFragment"); } } private void applyTheme(ThemeBundle themeToApply) { mThemeManager.apply(themeToApply, new Callback() { @Override public void onSuccess() { overridePendingTransition(R.anim.fade_in, R.anim.fade_out); Toast.makeText(getApplicationContext(), R.string.applied_theme_msg, Toast.LENGTH_LONG).show(); setResult(RESULT_THEME_APPLIED); finish(); } @Override public void onError(@Nullable Throwable throwable) { Log.w(TAG, "Error applying custom theme", throwable); Toast.makeText(CustomThemeActivity.this, R.string.apply_theme_error_msg, Toast.LENGTH_LONG).show(); } }); } private CustomThemeStepFragment getCurrentStepFragment() { return (CustomThemeStepFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_container); } @Override public void setCurrentStep(int i) { mCurrentStep = i; updateNavigationButtonLabels(); } private void updateNavigationButtonLabels() { mPreviousButton.setVisibility(mCurrentStep == 0 ? View.INVISIBLE : View.VISIBLE); mNextButton.setText((mCurrentStep < mSteps.size() -1) ? R.string.custom_theme_next : R.string.apply_btn); } @Override public void delete() { mThemeManager.removeCustomTheme(mCustomThemeManager.getOriginalTheme()); setResult(RESULT_THEME_DELETED); finish(); } @Override public void cancel() { finish(); } @Override public ThemeComponentOptionProvider getComponentOptionProvider( int position) { return mSteps.get(position).provider; } @Override public CustomThemeManager getCustomThemeManager() { return mCustomThemeManager; } @Override public void onUpArrowPressed() { // Skip it because CustomThemeStepFragment will implement cancel button // (instead of up arrow) on action bar. } @Override public boolean isUpArrowSupported() { // Skip it because CustomThemeStepFragment will implement cancel button // (instead of up arrow) on action bar. return false; } /** * Represents a step in selecting a custom theme, picking a particular component (eg font, * color, shape, etc). * Each step has a Fragment instance associated that instances of this class will provide. */ private static abstract class ComponentStep { @StringRes final int titleResId; @StringRes final int accessibilityResId; final ThemeComponentOptionProvider provider; final int position; private CustomThemeStepFragment mFragment; protected ComponentStep(@StringRes int titleResId, @StringRes int accessibilityResId, ThemeComponentOptionProvider provider, int position) { this.titleResId = titleResId; this.accessibilityResId = accessibilityResId; this.provider = provider; this.position = position; } CustomThemeStepFragment getFragment(String title) { if (mFragment == null) { mFragment = createFragment(title); } return mFragment; } /** * @return a newly created fragment that will handle this step's UI. */ abstract CustomThemeStepFragment createFragment(String title); } private class FontStep extends ComponentStep { protected FontStep(ThemeComponentOptionProvider provider, int position) { super(R.string.font_component_title, R.string.accessibility_custom_font_title, provider, position); } @Override CustomThemeComponentFragment createFragment(String title) { return CustomThemeComponentFragment.newInstance( title, position, titleResId, accessibilityResId); } } private class IconStep extends ComponentStep { protected IconStep(ThemeComponentOptionProvider provider, int position) { super(R.string.icon_component_title, R.string.accessibility_custom_icon_title, provider, position); } @Override CustomThemeComponentFragment createFragment(String title) { return CustomThemeComponentFragment.newInstance( title, position, titleResId, accessibilityResId); } } private class ColorStep extends ComponentStep { protected ColorStep(ThemeComponentOptionProvider provider, int position) { super(R.string.color_component_title, R.string.accessibility_custom_color_title, provider, position); } @Override CustomThemeComponentFragment createFragment(String title) { return CustomThemeComponentFragment.newInstance( title, position, titleResId, accessibilityResId); } } private class ShapeStep extends ComponentStep { protected ShapeStep(ThemeComponentOptionProvider provider, int position) { super(R.string.shape_component_title, R.string.accessibility_custom_shape_title, provider, position); } @Override CustomThemeComponentFragment createFragment(String title) { return CustomThemeComponentFragment.newInstance( title, position, titleResId, accessibilityResId); } } private class NameStep extends ComponentStep { protected NameStep(int position) { super(R.string.name_component_title, R.string.accessibility_custom_name_title, null, position); } @Override CustomThemeNameFragment createFragment(String title) { return CustomThemeNameFragment.newInstance( title, position, titleResId, accessibilityResId); } } }