1 /*
2  * Copyright (C) 2021 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.wallpaper.picker;
17 
18 import android.content.Context;
19 import android.os.Bundle;
20 import android.util.Log;
21 import android.view.LayoutInflater;
22 import android.view.View;
23 import android.view.ViewGroup;
24 
25 import androidx.annotation.Nullable;
26 import androidx.core.widget.NestedScrollView;
27 import androidx.fragment.app.Fragment;
28 import androidx.fragment.app.FragmentManager;
29 import androidx.lifecycle.ViewModelProvider;
30 
31 import com.android.wallpaper.R;
32 import com.android.wallpaper.model.CustomizationSectionController;
33 import com.android.wallpaper.model.CustomizationSectionController.CustomizationSectionNavigationController;
34 import com.android.wallpaper.model.PermissionRequester;
35 import com.android.wallpaper.model.WallpaperColorsViewModel;
36 import com.android.wallpaper.model.WallpaperPreviewNavigator;
37 import com.android.wallpaper.model.WorkspaceViewModel;
38 import com.android.wallpaper.module.CustomizationSections;
39 import com.android.wallpaper.module.InjectorProvider;
40 import com.android.wallpaper.util.ActivityUtils;
41 
42 import java.util.ArrayList;
43 import java.util.List;
44 import java.util.stream.Collectors;
45 
46 /** The Fragment UI for customization sections. */
47 public class CustomizationPickerFragment extends AppbarFragment implements
48         CustomizationSectionNavigationController {
49 
50     private static final String TAG = "CustomizationPickerFragment";
51     private static final String SCROLL_POSITION_Y = "SCROLL_POSITION_Y";
52 
53     // Note that the section views will be displayed by the list ordering.
54     private final List<CustomizationSectionController<?>> mSectionControllers = new ArrayList<>();
55     private NestedScrollView mNestedScrollView;
56     @Nullable private Bundle mBackStackSavedInstanceState;
57 
58     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, @Nullable Bundle savedInstanceState)59     public View onCreateView(LayoutInflater inflater, ViewGroup container,
60             @Nullable Bundle savedInstanceState) {
61         final View view = inflater.inflate(R.layout.collapsing_toolbar_base_layout,
62                 container, /* attachToRoot= */ false);
63         setContentView(view, R.layout.fragment_customization_picker);
64         setUpToolbar(view, ActivityUtils.isLaunchedFromSettingsRelated(getActivity().getIntent()));
65 
66         ViewGroup sectionContainer = view.findViewById(R.id.section_container);
67         sectionContainer.setOnApplyWindowInsetsListener((v, windowInsets) -> {
68             v.setPadding(
69                     v.getPaddingLeft(),
70                     v.getPaddingTop(),
71                     v.getPaddingRight(),
72                     windowInsets.getSystemWindowInsetBottom());
73             return windowInsets.consumeSystemWindowInsets();
74         });
75         mNestedScrollView = view.findViewById(R.id.scroll_container);
76 
77         if (mBackStackSavedInstanceState != null) {
78             savedInstanceState = mBackStackSavedInstanceState;
79             mBackStackSavedInstanceState = null;
80         }
81 
82         initSections(savedInstanceState);
83         mSectionControllers.forEach(controller ->
84                 mNestedScrollView.post(() -> {
85                             final Context context = getContext();
86                             if (context == null) {
87                                 Log.w(TAG, "Adding section views with null context");
88                                 return;
89                             }
90                             sectionContainer.addView(controller.createView(context));
91                         }
92                 )
93         );
94         final Bundle savedInstanceStateRef = savedInstanceState;
95         // Post it to the end of adding views to ensure restoring view state the last task.
96         mNestedScrollView.post(() ->
97                 restoreViewState(savedInstanceStateRef)
98         );
99         return view;
100     }
101 
setContentView(View view, int layoutResId)102     private void setContentView(View view, int layoutResId) {
103         final ViewGroup parent = view.findViewById(R.id.content_frame);
104         if (parent != null) {
105             parent.removeAllViews();
106         }
107         LayoutInflater.from(view.getContext()).inflate(layoutResId, parent);
108     }
109 
restoreViewState(@ullable Bundle savedInstanceState)110     private void restoreViewState(@Nullable Bundle savedInstanceState) {
111         if (savedInstanceState != null) {
112             mNestedScrollView.post(() ->
113                     mNestedScrollView.setScrollY(savedInstanceState.getInt(SCROLL_POSITION_Y)));
114         }
115     }
116 
117     @Override
onSaveInstanceState(Bundle savedInstanceState)118     public void onSaveInstanceState(Bundle savedInstanceState) {
119         onSaveInstanceStateInternal(savedInstanceState);
120         super.onSaveInstanceState(savedInstanceState);
121     }
122 
123     @Override
getToolbarId()124     protected int getToolbarId() {
125         return R.id.action_bar;
126     }
127 
128     @Override
getToolbarColorId()129     protected int getToolbarColorId() {
130         return android.R.color.transparent;
131     }
132 
133     @Override
getDefaultTitle()134     public CharSequence getDefaultTitle() {
135         return getString(R.string.app_name);
136     }
137 
138     @Override
onBackPressed()139     public boolean onBackPressed() {
140         // TODO(b/191120122) Improve glitchy animation in Settings.
141         if (ActivityUtils.isLaunchedFromSettingsSearch(getActivity().getIntent())) {
142             mSectionControllers.forEach(CustomizationSectionController::onTransitionOut);
143         }
144         return super.onBackPressed();
145     }
146 
147     @Override
onDestroyView()148     public void onDestroyView() {
149         // When add to back stack, #onDestroyView would be called, but #onDestroy wouldn't. So
150         // storing the state in variable to restore when back to foreground. If it's not a back
151         // stack case (i,e, config change), the variable would not be retained, see
152         // https://developer.android.com/guide/fragments/saving-state.
153         mBackStackSavedInstanceState = new Bundle();
154         onSaveInstanceStateInternal(mBackStackSavedInstanceState);
155 
156         mSectionControllers.forEach(CustomizationSectionController::release);
157         mSectionControllers.clear();
158         super.onDestroyView();
159     }
160 
161     @Override
navigateTo(Fragment fragment)162     public void navigateTo(Fragment fragment) {
163         FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
164         fragmentManager
165                 .beginTransaction()
166                 .replace(R.id.fragment_container, fragment)
167                 .addToBackStack(null)
168                 .commit();
169         fragmentManager.executePendingTransactions();
170     }
171 
172     /** Saves state of the fragment. */
onSaveInstanceStateInternal(Bundle savedInstanceState)173     private void onSaveInstanceStateInternal(Bundle savedInstanceState) {
174         if (mNestedScrollView != null) {
175             savedInstanceState.putInt(SCROLL_POSITION_Y, mNestedScrollView.getScrollY());
176         }
177         mSectionControllers.forEach(c -> c.onSaveInstanceState(savedInstanceState));
178     }
179 
initSections(@ullable Bundle savedInstanceState)180     private void initSections(@Nullable Bundle savedInstanceState) {
181         // Release and clear if any.
182         mSectionControllers.forEach(CustomizationSectionController::release);
183         mSectionControllers.clear();
184 
185         WallpaperColorsViewModel wcViewModel = new ViewModelProvider(getActivity())
186                 .get(WallpaperColorsViewModel.class);
187         WorkspaceViewModel workspaceViewModel = new ViewModelProvider(getActivity())
188                 .get(WorkspaceViewModel.class);
189 
190         CustomizationSections sections = InjectorProvider.getInjector().getCustomizationSections();
191         List<CustomizationSectionController<?>> allSectionControllers =
192                 sections.getAllSectionControllers(getActivity(), getViewLifecycleOwner(),
193                         wcViewModel, workspaceViewModel, getPermissionRequester(),
194                         getWallpaperPreviewNavigator(), this, savedInstanceState);
195 
196         mSectionControllers.addAll(getAvailableSections(allSectionControllers));
197     }
198 
getAvailableSections( List<CustomizationSectionController<?>> controllers)199     protected List<CustomizationSectionController<?>> getAvailableSections(
200             List<CustomizationSectionController<?>> controllers) {
201         return controllers.stream()
202                 .filter(controller -> {
203                     if(controller.isAvailable(getContext())) {
204                         return true;
205                     } else {
206                         controller.release();
207                         Log.d(TAG, "Section is not available: " + controller);
208                         return false;
209                     }})
210                 .collect(Collectors.toList());
211     }
212 
213     private PermissionRequester getPermissionRequester() {
214         return (PermissionRequester) getActivity();
215     }
216 
217     private WallpaperPreviewNavigator getWallpaperPreviewNavigator() {
218         return (WallpaperPreviewNavigator) getActivity();
219     }
220 }
221