1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.customization.picker.grid; 17 18 import static android.app.Activity.RESULT_OK; 19 20 import static com.android.customization.picker.ViewOnlyFullPreviewActivity.SECTION_GRID; 21 import static com.android.customization.picker.grid.GridFullPreviewFragment.EXTRA_GRID_OPTION; 22 import static com.android.customization.picker.grid.GridFullPreviewFragment.EXTRA_WALLPAPER_INFO; 23 import static com.android.wallpaper.widget.BottomActionBar.BottomAction.APPLY; 24 25 import android.content.Context; 26 import android.content.Intent; 27 import android.os.Bundle; 28 import android.util.Log; 29 import android.view.LayoutInflater; 30 import android.view.SurfaceView; 31 import android.view.View; 32 import android.view.ViewGroup; 33 import android.widget.Toast; 34 35 import androidx.annotation.NonNull; 36 import androidx.annotation.Nullable; 37 import androidx.core.widget.ContentLoadingProgressBar; 38 import androidx.recyclerview.widget.RecyclerView; 39 40 import com.android.customization.model.CustomizationManager.Callback; 41 import com.android.customization.model.CustomizationManager.OptionsFetchedListener; 42 import com.android.customization.model.CustomizationOption; 43 import com.android.customization.model.grid.GridOption; 44 import com.android.customization.model.grid.GridOptionsManager; 45 import com.android.customization.module.ThemesUserEventLogger; 46 import com.android.customization.picker.ViewOnlyFullPreviewActivity; 47 import com.android.customization.picker.WallpaperPreviewer; 48 import com.android.customization.widget.OptionSelectorController; 49 import com.android.wallpaper.R; 50 import com.android.wallpaper.model.WallpaperInfo; 51 import com.android.wallpaper.module.CurrentWallpaperInfoFactory; 52 import com.android.wallpaper.module.InjectorProvider; 53 import com.android.wallpaper.picker.AppbarFragment; 54 import com.android.wallpaper.widget.BottomActionBar; 55 56 import com.bumptech.glide.Glide; 57 58 import java.util.List; 59 60 /** 61 * Fragment that contains the UI for selecting and applying a GridOption. 62 */ 63 public class GridFragment extends AppbarFragment { 64 65 private static final int FULL_PREVIEW_REQUEST_CODE = 1000; 66 private static final String KEY_STATE_SELECTED_OPTION = "GridFragment.selectedOption"; 67 private static final String KEY_STATE_BOTTOM_ACTION_BAR_VISIBLE = 68 "GridFragment.bottomActionBarVisible"; 69 70 private static final String TAG = "GridFragment"; 71 72 /** 73 * Interface to be implemented by an Activity hosting a {@link GridFragment} 74 */ 75 public interface GridFragmentHost { getGridOptionsManager()76 GridOptionsManager getGridOptionsManager(); 77 } 78 newInstance(CharSequence title)79 public static GridFragment newInstance(CharSequence title) { 80 GridFragment fragment = new GridFragment(); 81 fragment.setArguments(AppbarFragment.createArguments(title)); 82 return fragment; 83 } 84 85 private WallpaperInfo mHomeWallpaper; 86 private RecyclerView mOptionsContainer; 87 private OptionSelectorController<GridOption> mOptionsController; 88 private GridOptionsManager mGridManager; 89 private GridOption mSelectedOption; 90 private ContentLoadingProgressBar mLoading; 91 private View mContent; 92 private View mError; 93 private BottomActionBar mBottomActionBar; 94 private ThemesUserEventLogger mEventLogger; 95 96 private GridOptionPreviewer mGridOptionPreviewer; 97 98 private final Callback mApplyGridCallback = new Callback() { 99 @Override 100 public void onSuccess() { 101 Toast.makeText(getContext(), R.string.applied_grid_msg, Toast.LENGTH_SHORT).show(); 102 getActivity().overridePendingTransition(R.anim.fade_in, R.anim.fade_out); 103 getActivity().finish(); 104 } 105 106 @Override 107 public void onError(@Nullable Throwable throwable) { 108 // Since we disabled it when clicked apply button. 109 mBottomActionBar.enableActions(); 110 mBottomActionBar.hide(); 111 //TODO(chihhangchuang): handle 112 } 113 }; 114 115 @Override onAttach(Context context)116 public void onAttach(Context context) { 117 super.onAttach(context); 118 mGridManager = ((GridFragmentHost) context).getGridOptionsManager(); 119 mEventLogger = (ThemesUserEventLogger) 120 InjectorProvider.getInjector().getUserEventLogger(context); 121 } 122 123 @Nullable 124 @Override onCreateView(@onNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)125 public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, 126 @Nullable Bundle savedInstanceState) { 127 View view = inflater.inflate( 128 R.layout.fragment_grid_picker, container, /* attachToRoot */ false); 129 setUpToolbar(view); 130 mContent = view.findViewById(R.id.content_section); 131 mOptionsContainer = view.findViewById(R.id.options_container); 132 mLoading = view.findViewById(R.id.loading_indicator); 133 mError = view.findViewById(R.id.error_section); 134 135 // Clear memory cache whenever grid fragment view is being loaded. 136 Glide.get(getContext()).clearMemory(); 137 setUpOptions(savedInstanceState); 138 139 SurfaceView wallpaperSurface = view.findViewById(R.id.wallpaper_preview_surface); 140 WallpaperPreviewer wallpaperPreviewer = new WallpaperPreviewer(getLifecycle(), 141 getActivity(), view.findViewById(R.id.wallpaper_preview_image), wallpaperSurface); 142 // Loads current Wallpaper. 143 CurrentWallpaperInfoFactory factory = InjectorProvider.getInjector() 144 .getCurrentWallpaperFactory(getContext().getApplicationContext()); 145 factory.createCurrentWallpaperInfos((homeWallpaper, lockWallpaper, presentationMode) -> { 146 mHomeWallpaper = homeWallpaper; 147 wallpaperPreviewer.setWallpaper(mHomeWallpaper, /* listener= */ null); 148 }, false); 149 150 mGridOptionPreviewer = new GridOptionPreviewer(mGridManager, 151 view.findViewById(R.id.grid_preview_container)); 152 153 view.findViewById(R.id.grid_preview_card).setOnClickListener(v -> showFullPreview()); 154 return view; 155 } 156 157 @Override onDestroy()158 public void onDestroy() { 159 super.onDestroy(); 160 if (mGridOptionPreviewer != null) { 161 mGridOptionPreviewer.release(); 162 } 163 } 164 165 @Override onSaveInstanceState(@onNull Bundle outState)166 public void onSaveInstanceState(@NonNull Bundle outState) { 167 super.onSaveInstanceState(outState); 168 if (mSelectedOption != null) { 169 outState.putParcelable(KEY_STATE_SELECTED_OPTION, mSelectedOption); 170 } 171 if (mBottomActionBar != null) { 172 outState.putBoolean(KEY_STATE_BOTTOM_ACTION_BAR_VISIBLE, mBottomActionBar.isVisible()); 173 } 174 } 175 176 @Override onActivityResult(int requestCode, int resultCode, @Nullable Intent data)177 public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { 178 super.onActivityResult(requestCode, resultCode, data); 179 if (requestCode == FULL_PREVIEW_REQUEST_CODE && resultCode == RESULT_OK) { 180 applyGridOption(data.getParcelableExtra(EXTRA_GRID_OPTION)); 181 } 182 } 183 184 185 @Override onBottomActionBarReady(BottomActionBar bottomActionBar)186 protected void onBottomActionBarReady(BottomActionBar bottomActionBar) { 187 mBottomActionBar = bottomActionBar; 188 mBottomActionBar.showActionsOnly(APPLY); 189 mBottomActionBar.setActionClickListener(APPLY, unused -> applyGridOption(mSelectedOption)); 190 } 191 applyGridOption(GridOption gridOption)192 private void applyGridOption(GridOption gridOption) { 193 mBottomActionBar.disableActions(); 194 mGridManager.apply(gridOption, mApplyGridCallback); 195 } 196 setUpOptions(@ullable Bundle savedInstanceState)197 private void setUpOptions(@Nullable Bundle savedInstanceState) { 198 hideError(); 199 mLoading.show(); 200 mGridManager.fetchOptions(new OptionsFetchedListener<GridOption>() { 201 @Override 202 public void onOptionsLoaded(List<GridOption> options) { 203 mLoading.hide(); 204 mOptionsController = new OptionSelectorController<>(mOptionsContainer, options); 205 mOptionsController.initOptions(mGridManager); 206 207 // Find the selected Grid option. 208 GridOption previouslySelectedOption = null; 209 if (savedInstanceState != null) { 210 previouslySelectedOption = findEquivalent( 211 options, savedInstanceState.getParcelable(KEY_STATE_SELECTED_OPTION)); 212 } 213 mSelectedOption = previouslySelectedOption != null 214 ? previouslySelectedOption 215 : getActiveOption(options); 216 217 mOptionsController.setSelectedOption(mSelectedOption); 218 onOptionSelected(mSelectedOption); 219 restoreBottomActionBarVisibility(savedInstanceState); 220 221 mOptionsController.addListener(selectedOption -> { 222 onOptionSelected(selectedOption); 223 mBottomActionBar.show(); 224 }); 225 } 226 227 @Override 228 public void onError(@Nullable Throwable throwable) { 229 if (throwable != null) { 230 Log.e(TAG, "Error loading grid options", throwable); 231 } 232 showError(); 233 } 234 }, false); 235 } 236 getActiveOption(List<GridOption> options)237 private GridOption getActiveOption(List<GridOption> options) { 238 return options.stream() 239 .filter(option -> option.isActive(mGridManager)) 240 .findAny() 241 // For development only, as there should always be a grid set. 242 .orElse(options.get(0)); 243 } 244 245 @Nullable findEquivalent(List<GridOption> options, GridOption target)246 private GridOption findEquivalent(List<GridOption> options, GridOption target) { 247 return options.stream() 248 .filter(option -> option.equals(target)) 249 .findAny() 250 .orElse(null); 251 } 252 hideError()253 private void hideError() { 254 mContent.setVisibility(View.VISIBLE); 255 mError.setVisibility(View.GONE); 256 } 257 showError()258 private void showError() { 259 mLoading.hide(); 260 mContent.setVisibility(View.GONE); 261 mError.setVisibility(View.VISIBLE); 262 } 263 onOptionSelected(CustomizationOption selectedOption)264 private void onOptionSelected(CustomizationOption selectedOption) { 265 mSelectedOption = (GridOption) selectedOption; 266 mEventLogger.logGridSelected(mSelectedOption); 267 mGridOptionPreviewer.setGridOption(mSelectedOption); 268 } 269 restoreBottomActionBarVisibility(@ullable Bundle savedInstanceState)270 private void restoreBottomActionBarVisibility(@Nullable Bundle savedInstanceState) { 271 boolean isBottomActionBarVisible = savedInstanceState != null 272 && savedInstanceState.getBoolean(KEY_STATE_BOTTOM_ACTION_BAR_VISIBLE); 273 if (isBottomActionBarVisible) { 274 mBottomActionBar.show(); 275 } else { 276 mBottomActionBar.hide(); 277 } 278 } 279 showFullPreview()280 private void showFullPreview() { 281 Bundle bundle = new Bundle(); 282 bundle.putParcelable(EXTRA_WALLPAPER_INFO, mHomeWallpaper); 283 bundle.putParcelable(EXTRA_GRID_OPTION, mSelectedOption); 284 Intent intent = ViewOnlyFullPreviewActivity.newIntent(getContext(), SECTION_GRID, bundle); 285 startActivityForResult(intent, FULL_PREVIEW_REQUEST_CODE); 286 } 287 } 288