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 com.android.wallpaper.widget.BottomActionBar.BottomAction.APPLY_TEXT;
19 
20 import android.content.Context;
21 import android.os.Bundle;
22 import android.util.Log;
23 import android.view.LayoutInflater;
24 import android.view.SurfaceView;
25 import android.view.View;
26 import android.view.ViewGroup;
27 import android.view.accessibility.AccessibilityManager;
28 import android.widget.Toast;
29 
30 import androidx.annotation.NonNull;
31 import androidx.annotation.Nullable;
32 import androidx.core.widget.ContentLoadingProgressBar;
33 import androidx.lifecycle.ViewModelProvider;
34 import androidx.recyclerview.widget.RecyclerView;
35 
36 import com.android.customization.model.CustomizationManager.Callback;
37 import com.android.customization.model.CustomizationManager.OptionsFetchedListener;
38 import com.android.customization.model.CustomizationOption;
39 import com.android.customization.model.grid.GridOption;
40 import com.android.customization.model.grid.GridOptionViewModel;
41 import com.android.customization.model.grid.GridOptionsManager;
42 import com.android.customization.module.ThemesUserEventLogger;
43 import com.android.customization.picker.WallpaperPreviewer;
44 import com.android.customization.widget.OptionSelectorController;
45 import com.android.customization.widget.OptionSelectorController.CheckmarkStyle;
46 import com.android.wallpaper.R;
47 import com.android.wallpaper.model.WallpaperInfo;
48 import com.android.wallpaper.module.CurrentWallpaperInfoFactory;
49 import com.android.wallpaper.module.InjectorProvider;
50 import com.android.wallpaper.picker.AppbarFragment;
51 import com.android.wallpaper.util.LaunchUtils;
52 import com.android.wallpaper.widget.BottomActionBar;
53 
54 import com.bumptech.glide.Glide;
55 
56 import java.util.List;
57 
58 /**
59  * Fragment that contains the UI for selecting and applying a GridOption.
60  */
61 public class GridFragment extends AppbarFragment {
62 
63     private static final String TAG = "GridFragment";
64 
65     private WallpaperInfo mHomeWallpaper;
66     private RecyclerView mOptionsContainer;
67     private OptionSelectorController<GridOption> mOptionsController;
68     private GridOptionsManager mGridManager;
69     private ContentLoadingProgressBar mLoading;
70     private View mContent;
71     private View mError;
72     private BottomActionBar mBottomActionBar;
73     private ThemesUserEventLogger mEventLogger;
74     private GridOptionPreviewer mGridOptionPreviewer;
75     private GridOptionViewModel mGridOptionViewModel;
76 
77     private final Callback mApplyGridCallback = new Callback() {
78         @Override
79         public void onSuccess() {
80             Toast.makeText(getContext(), R.string.applied_grid_msg, Toast.LENGTH_SHORT).show();
81             getActivity().overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
82             getActivity().finish();
83 
84             // Go back to launcher home
85             LaunchUtils.launchHome(getContext());
86         }
87 
88         @Override
89         public void onError(@Nullable Throwable throwable) {
90             // Since we disabled it when clicked apply button.
91             mBottomActionBar.enableActions();
92             mBottomActionBar.hide();
93             mGridOptionViewModel.setBottomActionBarVisible(false);
94             //TODO(chihhangchuang): handle
95         }
96     };
97 
98     @Override
onCreate(Bundle savedInstanceState)99     public void onCreate(Bundle savedInstanceState) {
100         super.onCreate(savedInstanceState);
101         mGridOptionViewModel = new ViewModelProvider(requireActivity()).get(
102                 GridOptionViewModel.class);
103     }
104 
105     @Nullable
106     @Override
onCreateView(@onNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)107     public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
108             @Nullable Bundle savedInstanceState) {
109         View view = inflater.inflate(
110                 R.layout.fragment_grid_picker, container, /* attachToRoot */ false);
111         setUpToolbar(view);
112         mContent = view.findViewById(R.id.content_section);
113         mOptionsContainer = view.findViewById(R.id.options_container);
114         AccessibilityManager accessibilityManager =
115                 (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
116         if (accessibilityManager.isEnabled()) {
117             // Make Talkback focus won't reset when notifyDataSetChange
118             mOptionsContainer.setItemAnimator(null);
119         }
120         mLoading = view.findViewById(R.id.loading_indicator);
121         mError = view.findViewById(R.id.error_section);
122 
123         // For nav bar edge-to-edge effect.
124         view.setOnApplyWindowInsetsListener((v, windowInsets) -> {
125             v.setPadding(
126                     v.getPaddingLeft(),
127                     windowInsets.getSystemWindowInsetTop(),
128                     v.getPaddingRight(),
129                     windowInsets.getSystemWindowInsetBottom());
130             return windowInsets.consumeSystemWindowInsets();
131         });
132 
133         // Clear memory cache whenever grid fragment view is being loaded.
134         Glide.get(getContext()).clearMemory();
135 
136         mGridManager = GridOptionsManager.getInstance(getContext());
137         mEventLogger = (ThemesUserEventLogger) InjectorProvider.getInjector()
138                 .getUserEventLogger(getContext());
139         setUpOptions();
140 
141         SurfaceView wallpaperSurface = view.findViewById(R.id.wallpaper_preview_surface);
142         WallpaperPreviewer wallpaperPreviewer = new WallpaperPreviewer(getLifecycle(),
143                 getActivity(), view.findViewById(R.id.wallpaper_preview_image), wallpaperSurface);
144         // Loads current Wallpaper.
145         CurrentWallpaperInfoFactory factory = InjectorProvider.getInjector()
146                 .getCurrentWallpaperFactory(getContext().getApplicationContext());
147         factory.createCurrentWallpaperInfos((homeWallpaper, lockWallpaper, presentationMode) -> {
148             mHomeWallpaper = homeWallpaper;
149             wallpaperPreviewer.setWallpaper(mHomeWallpaper, /* listener= */ null);
150         }, false);
151 
152         mGridOptionPreviewer = new GridOptionPreviewer(mGridManager,
153                 view.findViewById(R.id.grid_preview_container));
154 
155         return view;
156     }
157 
158     @Override
onBackPressed()159     public boolean onBackPressed() {
160         mGridOptionViewModel.setSelectedOption(null);
161         mGridOptionViewModel.setBottomActionBarVisible(false);
162         return super.onBackPressed();
163     }
164 
165     @Override
onDestroy()166     public void onDestroy() {
167         super.onDestroy();
168         if (mGridOptionPreviewer != null) {
169             mGridOptionPreviewer.release();
170         }
171     }
172 
173     @Override
getDefaultTitle()174     public CharSequence getDefaultTitle() {
175         return getString(R.string.grid_title);
176     }
177 
178     @Override
onBottomActionBarReady(BottomActionBar bottomActionBar)179     protected void onBottomActionBarReady(BottomActionBar bottomActionBar) {
180         super.onBottomActionBarReady(bottomActionBar);
181         mBottomActionBar = bottomActionBar;
182         mBottomActionBar.showActionsOnly(APPLY_TEXT);
183         mBottomActionBar.setActionClickListener(APPLY_TEXT,
184                 v -> applyGridOption(mGridOptionViewModel.getSelectedOption()));
185     }
186 
applyGridOption(GridOption gridOption)187     private void applyGridOption(GridOption gridOption) {
188         mBottomActionBar.disableActions();
189         mGridManager.apply(gridOption, mApplyGridCallback);
190     }
191 
setUpOptions()192     private void setUpOptions() {
193         hideError();
194         mLoading.show();
195         mGridManager.fetchOptions(new OptionsFetchedListener<GridOption>() {
196             @Override
197             public void onOptionsLoaded(List<GridOption> options) {
198                 mLoading.hide();
199                 mOptionsController = new OptionSelectorController<>(
200                         mOptionsContainer, options, /* useGrid= */ false,
201                         CheckmarkStyle.CENTER_CHANGE_COLOR_WHEN_NOT_SELECTED);
202                 mOptionsController.initOptions(mGridManager);
203                 GridOption previouslySelectedOption = findEquivalent(options,
204                         mGridOptionViewModel.getSelectedOption());
205                 mGridOptionViewModel.setSelectedOption(
206                         previouslySelectedOption != null
207                                 ? previouslySelectedOption
208                                 : getActiveOption(options));
209 
210                 mOptionsController.setSelectedOption(mGridOptionViewModel.getSelectedOption());
211                 onOptionSelected(mGridOptionViewModel.getSelectedOption());
212                 restoreBottomActionBarVisibility();
213 
214                 mOptionsController.addListener(selectedOption -> {
215                     String title = selectedOption.getTitle();
216                     int stringId = R.string.option_previewed_description;
217                     if (selectedOption.isActive(mGridManager)) {
218                         stringId = R.string.option_applied_previewed_description;
219                     }
220                     CharSequence cd = getContext().getString(stringId, title);
221                     mOptionsContainer.announceForAccessibility(cd);
222                     onOptionSelected(selectedOption);
223                     mBottomActionBar.show();
224                     mGridOptionViewModel.setBottomActionBarVisible(true);
225                 });
226             }
227 
228             @Override
229             public void onError(@Nullable Throwable throwable) {
230                 if (throwable != null) {
231                     Log.e(TAG, "Error loading grid options", throwable);
232                 }
233                 showError();
234             }
235         }, /*reload= */ true);
236     }
237 
getActiveOption(List<GridOption> options)238     private GridOption getActiveOption(List<GridOption> options) {
239         return options.stream()
240                 .filter(option -> option.isActive(mGridManager))
241                 .findAny()
242                 // For development only, as there should always be a grid set.
243                 .orElse(options.get(0));
244     }
245 
246     @Nullable
findEquivalent(List<GridOption> options, GridOption target)247     private GridOption findEquivalent(List<GridOption> options, GridOption target) {
248         return options.stream()
249                 .filter(option -> option.equals(target))
250                 .findAny()
251                 .orElse(null);
252     }
253 
hideError()254     private void hideError() {
255         mContent.setVisibility(View.VISIBLE);
256         mError.setVisibility(View.GONE);
257     }
258 
showError()259     private void showError() {
260         mLoading.hide();
261         mContent.setVisibility(View.GONE);
262         mError.setVisibility(View.VISIBLE);
263     }
264 
onOptionSelected(CustomizationOption selectedOption)265     private void onOptionSelected(CustomizationOption selectedOption) {
266         mGridOptionViewModel.setSelectedOption((GridOption) selectedOption);
267         mEventLogger.logGridSelected(mGridOptionViewModel.getSelectedOption());
268         mGridOptionPreviewer.setGridOption(mGridOptionViewModel.getSelectedOption());
269     }
270 
restoreBottomActionBarVisibility()271     private void restoreBottomActionBarVisibility() {
272         if (mGridOptionViewModel.getBottomActionBarVisible()) {
273             mBottomActionBar.show();
274         } else {
275             mBottomActionBar.hide();
276         }
277     }
278 }
279