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.content.res.Configuration.ORIENTATION_LANDSCAPE;
19 
20 import android.app.Activity;
21 import android.content.Context;
22 import android.content.res.Resources;
23 import android.graphics.drawable.BitmapDrawable;
24 import android.net.Uri;
25 import android.os.Bundle;
26 import android.util.DisplayMetrics;
27 import android.util.Log;
28 import android.view.LayoutInflater;
29 import android.view.SurfaceHolder;
30 import android.view.SurfaceView;
31 import android.view.View;
32 import android.view.View.OnLayoutChangeListener;
33 import android.view.ViewGroup;
34 import android.widget.ImageView;
35 
36 import androidx.annotation.NonNull;
37 import androidx.annotation.Nullable;
38 import androidx.cardview.widget.CardView;
39 import androidx.core.widget.ContentLoadingProgressBar;
40 import androidx.recyclerview.widget.RecyclerView;
41 
42 import com.android.customization.model.CustomizationManager.Callback;
43 import com.android.customization.model.CustomizationManager.OptionsFetchedListener;
44 import com.android.customization.model.grid.GridOption;
45 import com.android.customization.model.grid.GridOptionsManager;
46 import com.android.customization.module.ThemesUserEventLogger;
47 import com.android.customization.picker.BasePreviewAdapter;
48 import com.android.customization.picker.BasePreviewAdapter.PreviewPage;
49 import com.android.customization.widget.OptionSelectorController;
50 import com.android.systemui.shared.system.SurfaceViewRequestUtils;
51 import com.android.wallpaper.R;
52 import com.android.wallpaper.asset.Asset;
53 import com.android.wallpaper.asset.ContentUriAsset;
54 import com.android.wallpaper.model.WallpaperInfo;
55 import com.android.wallpaper.module.CurrentWallpaperInfoFactory;
56 import com.android.wallpaper.module.InjectorProvider;
57 import com.android.wallpaper.picker.ToolbarFragment;
58 import com.android.wallpaper.widget.PreviewPager;
59 
60 import com.bumptech.glide.Glide;
61 import com.bumptech.glide.request.RequestOptions;
62 
63 import java.util.List;
64 
65 /**
66  * Fragment that contains the UI for selecting and applying a GridOption.
67  */
68 public class GridFragment extends ToolbarFragment {
69 
70     private static final int PREVIEW_FADE_DURATION_MS = 100;
71 
72     private static final String TAG = "GridFragment";
73 
74     /**
75      * Interface to be implemented by an Activity hosting a {@link GridFragment}
76      */
77     public interface GridFragmentHost {
getGridOptionsManager()78         GridOptionsManager getGridOptionsManager();
79     }
80 
newInstance(CharSequence title)81     public static GridFragment newInstance(CharSequence title) {
82         GridFragment fragment = new GridFragment();
83         fragment.setArguments(ToolbarFragment.createArguments(title));
84         return fragment;
85     }
86 
87     private WallpaperInfo mHomeWallpaper;
88     private float mScreenAspectRatio;
89     private int mCardHeight;
90     private int mCardWidth;
91     private BitmapDrawable mCardBackground;
92     private GridPreviewAdapter mAdapter;
93     private RecyclerView mOptionsContainer;
94     private OptionSelectorController<GridOption> mOptionsController;
95     private GridOptionsManager mGridManager;
96     private GridOption mSelectedOption;
97     private PreviewPager mPreviewPager;
98     private ContentLoadingProgressBar mLoading;
99     private View mContent;
100     private View mError;
101     private ThemesUserEventLogger mEventLogger;
102 
103     @Override
onAttach(Context context)104     public void onAttach(Context context) {
105         super.onAttach(context);
106         mGridManager = ((GridFragmentHost) context).getGridOptionsManager();
107         mEventLogger = (ThemesUserEventLogger)
108                 InjectorProvider.getInjector().getUserEventLogger(context);
109     }
110 
111     @Nullable
112     @Override
onCreateView(@onNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)113     public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
114             @Nullable Bundle savedInstanceState) {
115         View view;
116         if (ADD_SCALABLE_HEADER) {
117             // TODO(b/147780560): Once the temporary flag (ADD_SCALABLE_HEADER) is removed,
118             // we should have a layout with the same name for portrait and landscape.
119             int orientation = getResources().getConfiguration().orientation;
120             view = inflater.inflate(
121                     orientation == ORIENTATION_LANDSCAPE
122                             ? R.layout.fragment_grid_picker
123                             : R.layout.fragment_grid_scalable_picker,
124                     container, /* attachToRoot */ false);
125         } else {
126             view = inflater.inflate(
127                     R.layout.fragment_grid_picker, container, /* attachToRoot */ false);
128         }
129         setUpToolbar(view);
130         mContent = view.findViewById(R.id.content_section);
131         mPreviewPager = view.findViewById(R.id.grid_preview_pager);
132         mOptionsContainer = view.findViewById(R.id.options_container);
133         mLoading = view.findViewById(R.id.loading_indicator);
134         mError = view.findViewById(R.id.error_section);
135         final Resources res = getResources();
136         DisplayMetrics dm = res.getDisplayMetrics();
137         mScreenAspectRatio = (float) dm.heightPixels / dm.widthPixels;
138 
139         // Clear memory cache whenever grid fragment view is being loaded.
140         Glide.get(getContext()).clearMemory();
141         setUpOptions();
142         view.findViewById(R.id.apply_button).setOnClickListener(v -> {
143             mGridManager.apply(mSelectedOption,  new Callback() {
144                 @Override
145                 public void onSuccess() {
146                     getActivity().finish();
147                 }
148 
149                 @Override
150                 public void onError(@Nullable Throwable throwable) {
151                     //TODO(santie): handle
152                 }
153             });
154 
155         });
156         CurrentWallpaperInfoFactory factory = InjectorProvider.getInjector()
157                 .getCurrentWallpaperFactory(getContext().getApplicationContext());
158 
159         factory.createCurrentWallpaperInfos((homeWallpaper, lockWallpaper, presentationMode) -> {
160             mHomeWallpaper = homeWallpaper;
161             loadWallpaperBackground();
162 
163         }, false);
164         view.addOnLayoutChangeListener(new OnLayoutChangeListener() {
165             @Override
166             public void onLayoutChange(View v, int left, int top, int right, int bottom,
167                     int oldLeft, int oldTop, int oldRight, int oldBottom) {
168                 mCardHeight = mPreviewPager.getHeight() - mPreviewPager.getPaddingTop() -
169                         res.getDimensionPixelSize(R.dimen.indicator_container_height);
170                 mCardWidth = (int) (mCardHeight / mScreenAspectRatio);
171                 view.removeOnLayoutChangeListener(this);
172                 loadWallpaperBackground();
173             }
174         });
175         return view;
176     }
177 
loadWallpaperBackground()178     private void loadWallpaperBackground() {
179         if (mHomeWallpaper != null && mCardHeight > 0 && mCardWidth > 0) {
180             mHomeWallpaper.getThumbAsset(getContext()).decodeBitmap(mCardWidth,
181                     mCardHeight,
182                     bitmap -> {
183                         mCardBackground =
184                                 new BitmapDrawable(getResources(), bitmap);
185                         if (mAdapter != null) {
186                             mAdapter.onWallpaperInfoLoaded();
187                         }
188                     });
189         }
190     }
191 
createAdapter()192     private void createAdapter() {
193         mAdapter = new GridPreviewAdapter(mSelectedOption);
194         mPreviewPager.setAdapter(mAdapter);
195     }
196 
setUpOptions()197     private void setUpOptions() {
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 
206                 mOptionsController.addListener(selected -> {
207                     mSelectedOption = (GridOption) selected;
208                     mEventLogger.logGridSelected(mSelectedOption);
209                     createAdapter();
210                 });
211                 mOptionsController.initOptions(mGridManager);
212                 for (GridOption option : options) {
213                     if (option.isActive(mGridManager)) {
214                         mSelectedOption = option;
215                     }
216                 }
217                 // For development only, as there should always be a grid set.
218                 if (mSelectedOption == null) {
219                     mSelectedOption = options.get(0);
220                 }
221                 createAdapter();
222             }
223 
224             @Override
225             public void onError(@Nullable Throwable throwable) {
226                 if (throwable != null) {
227                     Log.e(TAG, "Error loading grid options", throwable);
228                 }
229                 showError();
230             }
231         }, false);
232     }
233 
hideError()234     private void hideError() {
235         mContent.setVisibility(View.VISIBLE);
236         mError.setVisibility(View.GONE);
237     }
238 
showError()239     private void showError() {
240         mLoading.hide();
241         mContent.setVisibility(View.GONE);
242         mError.setVisibility(View.VISIBLE);
243     }
244 
245     private class GridPreviewPage extends PreviewPage {
246         private final int mPageId;
247         private final Asset mPreviewAsset;
248         private final int mCols;
249         private final int mRows;
250         private final Activity mActivity;
251 
252         private final String mName;
253 
254         private ImageView mPreview;
255         private SurfaceView mPreviewSurface;
256 
GridPreviewPage(Activity activity, int id, Uri previewUri, String name, int rows, int cols)257         private GridPreviewPage(Activity activity, int id, Uri previewUri, String name, int rows,
258                 int cols) {
259             super(null);
260             mPageId = id;
261             mPreviewAsset = new ContentUriAsset(activity, previewUri,
262                     RequestOptions.fitCenterTransform());
263             mName = name;
264             mRows = rows;
265             mCols = cols;
266             mActivity = activity;
267         }
268 
269         @Override
setCard(CardView card)270         public void setCard(CardView card) {
271             super.setCard(card);
272             mPreview = card.findViewById(R.id.grid_preview_image);
273             mPreviewSurface = card.findViewById(R.id.grid_preview_surface);
274         }
275 
bindPreviewContent()276         public void bindPreviewContent() {
277             Resources resources = card.getResources();
278             bindWallpaperIfAvailable();
279             final boolean usesSurfaceViewForPreview = mGridManager.usesSurfaceView();
280             mPreview.setVisibility(usesSurfaceViewForPreview ? View.GONE : View.VISIBLE);
281             mPreviewSurface.setVisibility(usesSurfaceViewForPreview ? View.VISIBLE : View.GONE);
282             if (usesSurfaceViewForPreview) {
283                 mPreviewSurface.setZOrderOnTop(true);
284                 mPreviewSurface.getHolder().addCallback(new SurfaceHolder.Callback() {
285                     @Override
286                     public void surfaceCreated(SurfaceHolder holder) {
287                         Bundle bundle = SurfaceViewRequestUtils.createSurfaceBundle(
288                                 mPreviewSurface);
289                         mGridManager.renderPreview(bundle, mName);
290                     }
291 
292                     @Override
293                     public void surfaceChanged(SurfaceHolder holder, int format, int width,
294                             int height) {}
295 
296                     @Override
297                     public void surfaceDestroyed(SurfaceHolder holder) {}
298                 });
299             } else {
300                 mPreviewAsset.loadDrawableWithTransition(mActivity,
301                         mPreview /* imageView */,
302                         PREVIEW_FADE_DURATION_MS /* duration */,
303                         null /* drawableLoadedListener */,
304                         resources.getColor(android.R.color.transparent,
305                                 null) /* placeHolderColorJ */);
306             }
307         }
308 
bindWallpaperIfAvailable()309         void bindWallpaperIfAvailable() {
310             if (card != null && mCardBackground != null) {
311                 mPreview.setBackground(mCardBackground);
312                 mPreviewSurface.setBackground(mCardBackground);
313             }
314         }
315     }
316     /**
317      * Adapter class for mPreviewPager.
318      * This is a ViewPager as it allows for a nice pagination effect (ie, pages snap on swipe,
319      * we don't want to just scroll)
320      */
321     class GridPreviewAdapter extends BasePreviewAdapter<GridPreviewPage> {
322 
GridPreviewAdapter(GridOption gridOption)323         GridPreviewAdapter(GridOption gridOption) {
324             super(getContext(), R.layout.grid_preview_card);
325             for (int i = 0; i < gridOption.previewPagesCount; i++) {
326                 addPage(new GridPreviewPage(getActivity(), i,
327                         gridOption.previewImageUri.buildUpon().appendPath("" + i).build(),
328                         gridOption.name, gridOption.rows, gridOption.cols));
329             }
330         }
331 
onWallpaperInfoLoaded()332         void onWallpaperInfoLoaded() {
333             for (GridPreviewPage page : mPages) {
334                 page.bindWallpaperIfAvailable();
335             }
336         }
337     }
338 }
339