blob: 5c5dcdd327e09865763efec48d1da61eaec02ec4 [file] [log] [blame]
/*
* 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.customization.picker.theme;
import static android.app.Activity.RESULT_OK;
import static com.android.customization.picker.ViewOnlyFullPreviewActivity.SECTION_STYLE;
import static com.android.customization.picker.theme.ThemeFullPreviewFragment.EXTRA_CAN_APPLY_FROM_FULL_PREVIEW;
import static com.android.customization.picker.theme.ThemeFullPreviewFragment.EXTRA_THEME_OPTION;
import static com.android.customization.picker.theme.ThemeFullPreviewFragment.EXTRA_THEME_OPTION_TITLE;
import static com.android.customization.picker.theme.ThemeFullPreviewFragment.EXTRA_WALLPAPER_INFO;
import static com.android.wallpaper.widget.BottomActionBar.BottomAction.APPLY;
import static com.android.wallpaper.widget.BottomActionBar.BottomAction.CUSTOMIZE;
import static com.android.wallpaper.widget.BottomActionBar.BottomAction.INFORMATION;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.widget.ContentLoadingProgressBar;
import androidx.recyclerview.widget.RecyclerView;
import com.android.customization.model.CustomizationManager.Callback;
import com.android.customization.model.CustomizationManager.OptionsFetchedListener;
import com.android.customization.model.theme.ThemeBundle;
import com.android.customization.model.theme.ThemeManager;
import com.android.customization.model.theme.custom.CustomTheme;
import com.android.customization.module.ThemesUserEventLogger;
import com.android.customization.picker.ViewOnlyFullPreviewActivity;
import com.android.customization.picker.WallpaperPreviewer;
import com.android.customization.widget.OptionSelectorController;
import com.android.customization.widget.ThemeInfoView;
import com.android.wallpaper.R;
import com.android.wallpaper.model.WallpaperInfo;
import com.android.wallpaper.module.CurrentWallpaperInfoFactory;
import com.android.wallpaper.module.InjectorProvider;
import com.android.wallpaper.picker.AppbarFragment;
import com.android.wallpaper.widget.BottomActionBar;
import com.android.wallpaper.widget.WallpaperColorsLoader;
import java.util.List;
/**
* Fragment that contains the main UI for selecting and applying a ThemeBundle.
*/
public class ThemeFragment extends AppbarFragment {
private static final String TAG = "ThemeFragment";
private static final String KEY_SELECTED_THEME = "ThemeFragment.SelectedThemeBundle";
private static final String KEY_STATE_BOTTOM_ACTION_BAR_VISIBILITY =
"ThemeFragment.bottomActionBarVisibility";
private static final int FULL_PREVIEW_REQUEST_CODE = 1000;
/**
* Interface to be implemented by an Activity hosting a {@link ThemeFragment}
*/
public interface ThemeFragmentHost {
ThemeManager getThemeManager();
}
public static ThemeFragment newInstance(CharSequence title) {
ThemeFragment fragment = new ThemeFragment();
fragment.setArguments(AppbarFragment.createArguments(title));
return fragment;
}
private RecyclerView mOptionsContainer;
private OptionSelectorController<ThemeBundle> mOptionsController;
private ThemeManager mThemeManager;
private ThemesUserEventLogger mEventLogger;
private ThemeBundle mSelectedTheme;
private ContentLoadingProgressBar mLoading;
private View mContent;
private View mError;
private WallpaperInfo mCurrentHomeWallpaper;
private CurrentWallpaperInfoFactory mCurrentWallpaperFactory;
private BottomActionBar mBottomActionBar;
private WallpaperPreviewer mWallpaperPreviewer;
private ImageView mWallpaperImage;
private ThemeOptionPreviewer mThemeOptionPreviewer;
private ThemeInfoView mThemeInfoView;
@Override
public void onAttach(Context context) {
super.onAttach(context);
mThemeManager = ((ThemeFragmentHost) context).getThemeManager();
mEventLogger = (ThemesUserEventLogger)
InjectorProvider.getInjector().getUserEventLogger(context);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(
R.layout.fragment_theme_picker, container, /* attachToRoot */ false);
setUpToolbar(view);
mContent = view.findViewById(R.id.content_section);
mLoading = view.findViewById(R.id.loading_indicator);
mError = view.findViewById(R.id.error_section);
mCurrentWallpaperFactory = InjectorProvider.getInjector()
.getCurrentWallpaperFactory(getActivity().getApplicationContext());
mOptionsContainer = view.findViewById(R.id.options_container);
// Set Wallpaper background.
mWallpaperImage = view.findViewById(R.id.wallpaper_preview_image);
mWallpaperPreviewer = new WallpaperPreviewer(
getLifecycle(),
getActivity(),
mWallpaperImage,
view.findViewById(R.id.wallpaper_preview_surface));
mCurrentWallpaperFactory.createCurrentWallpaperInfos(
(homeWallpaper, lockWallpaper, presentationMode) -> {
mCurrentHomeWallpaper = homeWallpaper;
mWallpaperPreviewer.setWallpaper(mCurrentHomeWallpaper);
updateThemePreviewColorPerWallpaper();
}, false);
ViewGroup previewContainer = view.findViewById(R.id.theme_preview_container);
previewContainer.setOnClickListener(v -> showFullPreview());
mThemeOptionPreviewer = new ThemeOptionPreviewer(
getLifecycle(),
getContext(),
previewContainer);
view.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
mWallpaperPreviewer.updatePreviewCardRadius();
updateThemePreviewColorPerWallpaper();
view.removeOnLayoutChangeListener(this);
}
});
return view;
}
@Override
protected void onBottomActionBarReady(BottomActionBar bottomActionBar) {
mBottomActionBar = bottomActionBar;
mBottomActionBar.showActionsOnly(INFORMATION, APPLY);
mBottomActionBar.setActionClickListener(APPLY, v -> {
mBottomActionBar.disableActions();
applyTheme();
});
mThemeInfoView = (ThemeInfoView) LayoutInflater.from(getContext()).inflate(
R.layout.theme_info_view, /* root= */ null);
mBottomActionBar.attachViewToBottomSheetAndBindAction(mThemeInfoView, INFORMATION);
mBottomActionBar.setActionClickListener(CUSTOMIZE, this::onCustomizeClicked);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Setup options here when all views are ready(including BottomActionBar), since we need to
// update views after options are loaded.
setUpOptions(savedInstanceState);
}
private void updateThemePreviewColorPerWallpaper() {
if (getContext() == null) {
return;
}
if (mCurrentHomeWallpaper != null && mWallpaperImage.getMeasuredWidth() > 0
&& mWallpaperImage.getMeasuredHeight() > 0) {
WallpaperColorsLoader.getWallpaperColors(
getContext(),
mCurrentHomeWallpaper.getThumbAsset(getContext()),
mWallpaperImage.getMeasuredWidth(),
mWallpaperImage.getMeasuredHeight(),
mThemeOptionPreviewer::updateColorForLauncherWidgets);
}
}
private void applyTheme() {
mThemeManager.apply(mSelectedTheme, new Callback() {
@Override
public void onSuccess() {
Toast.makeText(getContext(), R.string.applied_theme_msg, Toast.LENGTH_LONG).show();
getActivity().overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
getActivity().finish();
}
@Override
public void onError(@Nullable Throwable throwable) {
Log.w(TAG, "Error applying theme", throwable);
// Since we disabled it when clicked apply button.
mBottomActionBar.enableActions();
mBottomActionBar.hide();
Toast.makeText(getContext(), R.string.apply_theme_error_msg,
Toast.LENGTH_LONG).show();
}
});
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
if (mSelectedTheme != null && !mSelectedTheme.isActive(mThemeManager)) {
outState.putString(KEY_SELECTED_THEME, mSelectedTheme.getSerializedPackages());
}
if (mBottomActionBar != null) {
outState.putBoolean(KEY_STATE_BOTTOM_ACTION_BAR_VISIBILITY,
mBottomActionBar.isVisible());
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == CustomThemeActivity.REQUEST_CODE_CUSTOM_THEME) {
if (resultCode == CustomThemeActivity.RESULT_THEME_DELETED) {
mSelectedTheme = null;
reloadOptions();
} else if (resultCode == CustomThemeActivity.RESULT_THEME_APPLIED) {
getActivity().overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
getActivity().finish();
} else {
if (mSelectedTheme != null) {
mOptionsController.setSelectedOption(mSelectedTheme);
// Set selected option above will show BottomActionBar,
// hide BottomActionBar for the mis-trigger.
mBottomActionBar.hide();
} else {
reloadOptions();
}
}
} else if (requestCode == FULL_PREVIEW_REQUEST_CODE && resultCode == RESULT_OK) {
applyTheme();
}
super.onActivityResult(requestCode, resultCode, data);
}
private void onCustomizeClicked(View view) {
if (mSelectedTheme instanceof CustomTheme) {
navigateToCustomTheme((CustomTheme) mSelectedTheme);
}
}
private void hideError() {
mContent.setVisibility(View.VISIBLE);
mError.setVisibility(View.GONE);
}
private void showError() {
mLoading.hide();
mContent.setVisibility(View.GONE);
mError.setVisibility(View.VISIBLE);
}
private void setUpOptions(@Nullable Bundle savedInstanceState) {
hideError();
mLoading.show();
mThemeManager.fetchOptions(new OptionsFetchedListener<ThemeBundle>() {
@Override
public void onOptionsLoaded(List<ThemeBundle> options) {
mOptionsController = new OptionSelectorController<>(mOptionsContainer, options);
mOptionsController.addListener(selected -> {
mLoading.hide();
if (selected instanceof CustomTheme && !((CustomTheme) selected).isDefined()) {
navigateToCustomTheme((CustomTheme) selected);
} else {
mSelectedTheme = (ThemeBundle) selected;
mSelectedTheme.setOverrideThemeWallpaper(mCurrentHomeWallpaper);
mEventLogger.logThemeSelected(mSelectedTheme,
selected instanceof CustomTheme);
mThemeOptionPreviewer.setPreviewInfo(mSelectedTheme.getPreviewInfo());
if (mThemeInfoView != null && mSelectedTheme != null) {
mThemeInfoView.populateThemeInfo(mSelectedTheme);
}
if (selected instanceof CustomTheme) {
mBottomActionBar.showActionsOnly(INFORMATION, CUSTOMIZE, APPLY);
} else {
mBottomActionBar.showActionsOnly(INFORMATION, APPLY);
}
mBottomActionBar.show();
}
});
mOptionsController.initOptions(mThemeManager);
String previouslySelected = savedInstanceState != null
? savedInstanceState.getString(KEY_SELECTED_THEME) : null;
ThemeBundle previouslySelectedTheme = null;
ThemeBundle activeTheme = null;
for (ThemeBundle theme : options) {
if (previouslySelected != null
&& previouslySelected.equals(theme.getSerializedPackages())) {
previouslySelectedTheme = theme;
}
if (theme.isActive(mThemeManager)) {
activeTheme = theme;
}
}
mSelectedTheme = previouslySelectedTheme != null
? previouslySelectedTheme
: activeTheme;
if (mSelectedTheme == null) {
// Select the default theme if there is no matching custom enabled theme
mSelectedTheme = findDefaultThemeBundle(options);
}
mOptionsController.setSelectedOption(mSelectedTheme);
boolean bottomActionBarVisibility = savedInstanceState != null
&& savedInstanceState.getBoolean(KEY_STATE_BOTTOM_ACTION_BAR_VISIBILITY);
if (bottomActionBarVisibility) {
mBottomActionBar.show();
} else {
mBottomActionBar.hide();
}
}
@Override
public void onError(@Nullable Throwable throwable) {
if (throwable != null) {
Log.e(TAG, "Error loading theme bundles", throwable);
}
showError();
}
}, false);
}
private void reloadOptions() {
mThemeManager.fetchOptions(options -> {
mOptionsController.resetOptions(options);
for (ThemeBundle theme : options) {
if (theme.isActive(mThemeManager)) {
mSelectedTheme = theme;
break;
}
}
if (mSelectedTheme == null) {
// Select the default theme if there is no matching custom enabled theme
mSelectedTheme = findDefaultThemeBundle(options);
}
mOptionsController.setSelectedOption(mSelectedTheme);
// Set selected option above will show BottomActionBar,
// hide BottomActionBar for the mis-trigger.
mBottomActionBar.hide();
}, true);
}
private ThemeBundle findDefaultThemeBundle(List<ThemeBundle> options) {
String defaultThemeTitle =
getActivity().getResources().getString(R.string.default_theme_title);
for (ThemeBundle bundle : options) {
if (bundle.getTitle().equals(defaultThemeTitle)) {
return bundle;
}
}
return null;
}
private void navigateToCustomTheme(CustomTheme themeToEdit) {
Intent intent = new Intent(getActivity(), CustomThemeActivity.class);
intent.putExtra(CustomThemeActivity.EXTRA_THEME_TITLE, themeToEdit.getTitle());
intent.putExtra(CustomThemeActivity.EXTRA_THEME_ID, themeToEdit.getId());
intent.putExtra(CustomThemeActivity.EXTRA_THEME_PACKAGES,
themeToEdit.getSerializedPackages());
intent.putExtra(CustomThemeActivity.CREATE_NEW_THEME, !themeToEdit.isDefined());
startActivityForResult(intent, CustomThemeActivity.REQUEST_CODE_CUSTOM_THEME);
}
private void showFullPreview() {
Bundle bundle = new Bundle();
bundle.putParcelable(EXTRA_WALLPAPER_INFO, mCurrentHomeWallpaper);
bundle.putString(EXTRA_THEME_OPTION, mSelectedTheme.getSerializedPackages());
bundle.putString(EXTRA_THEME_OPTION_TITLE, mSelectedTheme.getTitle());
bundle.putBoolean(EXTRA_CAN_APPLY_FROM_FULL_PREVIEW, true);
Intent intent = ViewOnlyFullPreviewActivity.newIntent(getContext(), SECTION_STYLE, bundle);
startActivityForResult(intent, FULL_PREVIEW_REQUEST_CODE);
}
}