1 /*
2  * Copyright (C) 2017 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 static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED;
19 import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED;
20 
21 import android.app.Activity;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.res.ColorStateList;
25 import android.content.res.Resources.NotFoundException;
26 import android.content.res.TypedArray;
27 import android.graphics.Insets;
28 import android.graphics.drawable.GradientDrawable;
29 import android.net.Uri;
30 import android.os.Bundle;
31 import android.util.Log;
32 import android.view.ContextThemeWrapper;
33 import android.view.LayoutInflater;
34 import android.view.Menu;
35 import android.view.MenuInflater;
36 import android.view.MenuItem;
37 import android.view.View;
38 import android.view.View.OnClickListener;
39 import android.view.ViewGroup;
40 import android.view.WindowInsets;
41 import android.widget.Button;
42 import android.widget.CheckBox;
43 import android.widget.TextView;
44 import android.widget.Toast;
45 
46 import androidx.annotation.CallSuper;
47 import androidx.annotation.IdRes;
48 import androidx.annotation.IntDef;
49 import androidx.annotation.LayoutRes;
50 import androidx.annotation.Nullable;
51 import androidx.appcompat.app.AppCompatActivity;
52 import androidx.appcompat.widget.Toolbar;
53 import androidx.core.view.ViewCompat;
54 import androidx.core.widget.ContentLoadingProgressBar;
55 import androidx.fragment.app.Fragment;
56 import androidx.fragment.app.FragmentActivity;
57 
58 import com.android.wallpaper.R;
59 import com.android.wallpaper.model.LiveWallpaperInfo;
60 import com.android.wallpaper.model.WallpaperInfo;
61 import com.android.wallpaper.module.ExploreIntentChecker;
62 import com.android.wallpaper.module.Injector;
63 import com.android.wallpaper.module.InjectorProvider;
64 import com.android.wallpaper.module.UserEventLogger;
65 import com.android.wallpaper.module.WallpaperPersister.Destination;
66 import com.android.wallpaper.module.WallpaperPreferences;
67 import com.android.wallpaper.module.WallpaperSetter;
68 
69 import com.google.android.material.bottomsheet.BottomSheetBehavior;
70 import com.google.android.material.bottomsheet.BottomSheetBehavior.State;
71 
72 import java.util.Date;
73 import java.util.List;
74 
75 /**
76  * Base Fragment to display the UI for previewing an individual wallpaper
77  */
78 public abstract class PreviewFragment extends Fragment implements
79         SetWallpaperDialogFragment.Listener, SetWallpaperErrorDialogFragment.Listener,
80         LoadWallpaperErrorDialogFragment.Listener {
81 
82     /**
83      * User can view wallpaper and attributions in full screen, but "Set wallpaper" button is
84      * hidden.
85      */
86     static final int MODE_VIEW_ONLY = 0;
87 
88     /**
89      * User can view wallpaper and attributions in full screen and click "Set wallpaper" to set the
90      * wallpaper with pan and crop position to the device.
91      */
92     static final int MODE_CROP_AND_SET_WALLPAPER = 1;
93 
94     /**
95      * Possible preview modes for the fragment.
96      */
97     @IntDef({
98             MODE_VIEW_ONLY,
99             MODE_CROP_AND_SET_WALLPAPER})
100     public @interface PreviewMode {
101     }
102 
103     public static final String ARG_WALLPAPER = "wallpaper";
104     public static final String ARG_PREVIEW_MODE = "preview_mode";
105     public static final String ARG_TESTING_MODE_ENABLED = "testing_mode_enabled";
106 
107     /**
108      * Creates and returns new instance of {@link ImagePreviewFragment} with the provided wallpaper
109      * set as an argument.
110      */
newInstance( WallpaperInfo wallpaperInfo, @PreviewMode int mode, boolean testingModeEnabled)111     public static PreviewFragment newInstance(
112             WallpaperInfo wallpaperInfo, @PreviewMode int mode, boolean testingModeEnabled) {
113 
114         boolean isLive = wallpaperInfo instanceof LiveWallpaperInfo;
115 
116         Bundle args = new Bundle();
117         args.putParcelable(ARG_WALLPAPER, wallpaperInfo);
118         args.putInt(ARG_PREVIEW_MODE, mode);
119         args.putBoolean(ARG_TESTING_MODE_ENABLED, testingModeEnabled);
120 
121         PreviewFragment fragment = isLive ? new LivePreviewFragment() : new ImagePreviewFragment();
122         fragment.setArguments(args);
123         return fragment;
124     }
125 
126     private static final String TAG_LOAD_WALLPAPER_ERROR_DIALOG_FRAGMENT =
127             "load_wallpaper_error_dialog";
128     private static final String TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT =
129             "set_wallpaper_error_dialog";
130     private static final int UNUSED_REQUEST_CODE = 1;
131     private static final String TAG = "PreviewFragment";
132     static final String KEY_BOTTOM_SHEET_STATE = "key_bottom_sheet_state";
133 
134     @PreviewMode
135     protected int mPreviewMode;
136 
137     /**
138      * When true, enables a test mode of operation -- in which certain UI features are disabled to
139      * allow for UI tests to run correctly. Works around issue in ProgressDialog currently where the
140      * dialog constantly keeps the UI thread alive and blocks a test forever.
141      */
142     protected boolean mTestingModeEnabled;
143 
144     protected WallpaperInfo mWallpaper;
145     protected WallpaperSetter mWallpaperSetter;
146     protected UserEventLogger mUserEventLogger;
147     protected ViewGroup mBottomSheet;
148     protected ContentLoadingProgressBar mLoadingProgressBar;
149 
150     protected CheckBox mPreview;
151 
152     @SuppressWarnings("RestrictTo")
153     @State
154     protected int mBottomSheetInitialState;
155 
156     protected Intent mExploreIntent;
157 
158     /**
159      * Staged error dialog fragments that were unable to be shown when the hosting activity didn't
160      * allow committing fragment transactions.
161      */
162     private SetWallpaperErrorDialogFragment mStagedSetWallpaperErrorDialogFragment;
163     private LoadWallpaperErrorDialogFragment mStagedLoadWallpaperErrorDialogFragment;
164 
getAttrColor(Context context, int attr)165     protected static int getAttrColor(Context context, int attr) {
166         TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
167         int colorAccent = ta.getColor(0, 0);
168         ta.recycle();
169         return colorAccent;
170     }
171 
172     @Override
onCreate(Bundle savedInstanceState)173     public void onCreate(Bundle savedInstanceState) {
174         super.onCreate(savedInstanceState);
175 
176         Activity activity = getActivity();
177         Context appContext = getContext().getApplicationContext();
178         Injector injector = InjectorProvider.getInjector();
179 
180         mUserEventLogger = injector.getUserEventLogger(appContext);
181         mWallpaper = getArguments().getParcelable(ARG_WALLPAPER);
182 
183         //noinspection ResourceType
184         mPreviewMode = getArguments().getInt(ARG_PREVIEW_MODE);
185         mTestingModeEnabled = getArguments().getBoolean(ARG_TESTING_MODE_ENABLED);
186         mWallpaperSetter = new WallpaperSetter(injector.getWallpaperPersister(appContext),
187                 injector.getPreferences(appContext), mUserEventLogger, mTestingModeEnabled);
188 
189         setHasOptionsMenu(true);
190 
191         List<String> attributions = getAttributions(activity);
192         if (attributions.size() > 0 && attributions.get(0) != null) {
193             activity.setTitle(attributions.get(0));
194         }
195     }
196 
197     @Override
198     @CallSuper
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)199     public View onCreateView(LayoutInflater inflater, ViewGroup container,
200                              Bundle savedInstanceState) {
201         View view = inflater.inflate(getLayoutResId(), container, false);
202 
203         // Set toolbar as the action bar.
204         Toolbar toolbar = view.findViewById(R.id.toolbar);
205         AppCompatActivity activity = (AppCompatActivity) requireActivity();
206         activity.setSupportActionBar(toolbar);
207         activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
208         activity.getSupportActionBar().setDisplayShowTitleEnabled(false);
209 
210         toolbar.getNavigationIcon().setTint(getAttrColor(activity, android.R.attr.colorPrimary));
211         toolbar.getNavigationIcon().setAutoMirrored(true);
212 
213         ViewCompat.setPaddingRelative(toolbar,
214         /* start */ getResources().getDimensionPixelSize(
215                         R.dimen.preview_toolbar_up_button_start_padding),
216         /* top */ 0,
217         /* end */ getResources().getDimensionPixelSize(
218                         R.dimen.preview_toolbar_set_wallpaper_button_end_padding),
219         /* bottom */ 0);
220 
221         mLoadingProgressBar = view.findViewById(getLoadingIndicatorResId());
222         mLoadingProgressBar.show();
223 
224         mBottomSheet = view.findViewById(getBottomSheetResId());
225         setUpBottomSheetView(mBottomSheet);
226 
227         // Workaround as we don't have access to bottomDialogCornerRadius, mBottomSheet radii are
228         // set to dialogCornerRadius by default.
229         GradientDrawable bottomSheetBackground = (GradientDrawable) mBottomSheet.getBackground();
230         float[] radii = bottomSheetBackground.getCornerRadii();
231         for (int i = 0; i < radii.length; i++) {
232             radii[i]*=2f;
233         }
234         bottomSheetBackground = ((GradientDrawable)bottomSheetBackground.mutate());
235         bottomSheetBackground.setCornerRadii(radii);
236         mBottomSheet.setBackground(bottomSheetBackground);
237 
238         mBottomSheetInitialState = (savedInstanceState == null) ? STATE_EXPANDED
239                 : savedInstanceState.getInt(KEY_BOTTOM_SHEET_STATE, STATE_EXPANDED);
240         setUpBottomSheetListeners();
241 
242         view.setOnApplyWindowInsetsListener((v, windowInsets) -> {
243             toolbar.setPadding(toolbar.getPaddingLeft(),
244                     toolbar.getPaddingTop() + windowInsets.getSystemWindowInsetTop(),
245                     toolbar.getPaddingRight(), toolbar.getBottom());
246             mBottomSheet.setPadding(mBottomSheet.getPaddingLeft(),
247                     mBottomSheet.getPaddingTop(), mBottomSheet.getPaddingRight(),
248                     mBottomSheet.getPaddingBottom() + windowInsets.getSystemWindowInsetBottom());
249             WindowInsets.Builder builder = new WindowInsets.Builder(windowInsets);
250             builder.setSystemWindowInsets(Insets.of(windowInsets.getSystemWindowInsetLeft(),
251                     0, windowInsets.getStableInsetRight(), 0));
252             return builder.build();
253         });
254 
255         return view;
256     }
257 
populateInfoPage(InfoPageController infoPage)258     protected void populateInfoPage(InfoPageController infoPage) {
259         Context context = requireContext();
260 
261         BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(mBottomSheet);
262 
263         List<String> attributions = getAttributions(context);
264         boolean showMetadata = shouldShowMetadataInPreview();
265         CharSequence exploreLabel = getExploreButtonLabel(context);
266 
267         infoPage.populate(attributions, showMetadata, this::onSetWallpaperClicked,
268                 exploreLabel,
269                 (showMetadata && mExploreIntent != null) ? this::onExploreClicked : null);
270 
271         mBottomSheet.setVisibility(View.VISIBLE);
272 
273         // Initialize the state of the BottomSheet based on the current state because if the initial
274         // and current state are the same, the state change listener won't fire and set the correct
275         // arrow asset and text alpha.
276         if (mBottomSheetInitialState == STATE_EXPANDED) {
277             setPreviewChecked(false);
278             infoPage.setContentAlpha(1f);
279         } else {
280             setPreviewChecked(true);
281             infoPage.setContentAlpha(0f);
282         }
283 
284         bottomSheetBehavior.setState(mBottomSheetInitialState);
285     }
286 
getAttributions(Context context)287     protected List<String> getAttributions(Context context) {
288         return mWallpaper.getAttributions(context);
289     }
290 
shouldShowMetadataInPreview()291     protected boolean shouldShowMetadataInPreview() {
292         android.app.WallpaperInfo wallpaperComponent = mWallpaper.getWallpaperComponent();
293         return wallpaperComponent == null || wallpaperComponent.getShowMetadataInPreview();
294     }
295 
296     @Nullable
getExploreButtonLabel(Context context)297     protected abstract CharSequence getExploreButtonLabel(Context context);
298 
299     @LayoutRes
getLayoutResId()300     protected abstract int getLayoutResId();
301 
setUpBottomSheetView(ViewGroup bottomSheet)302     protected abstract void setUpBottomSheetView(ViewGroup bottomSheet);
303 
304     @IdRes
getBottomSheetResId()305     protected abstract int getBottomSheetResId();
306 
307     @IdRes
getLoadingIndicatorResId()308     protected abstract int getLoadingIndicatorResId();
309 
getDeviceDefaultTheme()310     protected int getDeviceDefaultTheme() {
311         return android.R.style.Theme_DeviceDefault;
312     }
313 
314     @Override
onResume()315     public void onResume() {
316         super.onResume();
317 
318         WallpaperPreferences preferences =
319                 InjectorProvider.getInjector().getPreferences(getActivity());
320         preferences.setLastAppActiveTimestamp(new Date().getTime());
321 
322         // Show the staged 'load wallpaper' or 'set wallpaper' error dialog fragments if there is
323         // one that was unable to be shown earlier when this fragment's hosting activity didn't
324         // allow committing fragment transactions.
325         if (mStagedLoadWallpaperErrorDialogFragment != null) {
326             mStagedLoadWallpaperErrorDialogFragment.show(
327                     requireFragmentManager(), TAG_LOAD_WALLPAPER_ERROR_DIALOG_FRAGMENT);
328             mStagedLoadWallpaperErrorDialogFragment = null;
329         }
330         if (mStagedSetWallpaperErrorDialogFragment != null) {
331             mStagedSetWallpaperErrorDialogFragment.show(
332                     requireFragmentManager(), TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT);
333             mStagedSetWallpaperErrorDialogFragment = null;
334         }
335     }
336 
337     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)338     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
339         super.onCreateOptionsMenu(menu, inflater);
340         inflater.inflate(R.menu.preview_menu, menu);
341     }
342 
343     @Override
onPrepareOptionsMenu(Menu menu)344     public void onPrepareOptionsMenu(Menu menu) {
345         super.onPrepareOptionsMenu(menu);
346         setupPreviewMenu(menu);
347     }
348 
349     @Override
onOptionsItemSelected(MenuItem item)350     public boolean onOptionsItemSelected(MenuItem item) {
351         int id = item.getItemId();
352         if (id == android.R.id.home) {
353             // The Preview screen has multiple entry points. It could be opened from either
354             // the IndividualPreviewActivity, the "My photos" selection (by way of
355             // TopLevelPickerActivity), or from a system "crop and set wallpaper" intent.
356             // Therefore, handle the Up button as a global Back.
357             requireActivity().onBackPressed();
358             return true;
359         }
360 
361         return false;
362     }
363 
setupPreviewMenu(Menu menu)364     private void setupPreviewMenu(Menu menu) {
365         mPreview = (CheckBox) menu.findItem(R.id.preview).getActionView();
366         mPreview.setChecked(mBottomSheetInitialState == STATE_COLLAPSED);
367         mPreview.setOnClickListener(this::setPreviewBehavior);
368     }
369 
setPreviewChecked(boolean checked)370     protected void setPreviewChecked(boolean checked) {
371         if (mPreview != null) {
372             mPreview.setChecked(checked);
373             int resId = checked ? R.string.expand_attribution_panel
374                     : R.string.collapse_attribution_panel;
375             mPreview.setContentDescription(getResources().getString(resId));
376         }
377     }
378 
setPreviewBehavior(View v)379     private void setPreviewBehavior(View v) {
380         CheckBox checkbox = (CheckBox) v;
381         BottomSheetBehavior<?> behavior = BottomSheetBehavior.from(mBottomSheet);
382 
383         if (checkbox.isChecked()) {
384             behavior.setState(STATE_COLLAPSED);
385         } else {
386             behavior.setState(STATE_EXPANDED);
387         }
388     }
389 
setUpExploreIntent(@ullable Runnable callback)390     protected void setUpExploreIntent(@Nullable Runnable callback) {
391         Context context = getContext();
392         if (context == null) {
393             return;
394         }
395         String actionUrl = mWallpaper.getActionUrl(context);
396         if (actionUrl != null && !actionUrl.isEmpty()) {
397             Uri exploreUri = Uri.parse(mWallpaper.getActionUrl(context));
398             ExploreIntentChecker intentChecker =
399                     InjectorProvider.getInjector().getExploreIntentChecker(context);
400 
401             intentChecker.fetchValidActionViewIntent(exploreUri, exploreIntent -> {
402                 if (getActivity() == null) {
403                     return;
404                 }
405 
406                 mExploreIntent = exploreIntent;
407                 if (callback != null) {
408                     callback.run();
409                 }
410             });
411         } else {
412             if (callback != null) {
413                 callback.run();
414             }
415         }
416     }
417 
418     /**
419      * Configure loading indicator with a MaterialProgressDrawable.
420      */
setUpLoadingIndicator()421     protected void setUpLoadingIndicator() {
422         mLoadingProgressBar.setProgressTintList(ColorStateList.valueOf(getAttrColor(
423                 new ContextThemeWrapper(requireContext(), getDeviceDefaultTheme()),
424                 android.R.attr.colorAccent)));
425         mLoadingProgressBar.show();
426     }
427 
isLoaded()428     protected abstract boolean isLoaded();
429 
430     @Override
onSet(int destination)431     public void onSet(int destination) {
432         setCurrentWallpaper(destination);
433     }
434 
435     @Override
onClickTryAgain(@estination int wallpaperDestination)436     public void onClickTryAgain(@Destination int wallpaperDestination) {
437         setCurrentWallpaper(wallpaperDestination);
438     }
439 
440     @Override
onClickOk()441     public void onClickOk() {
442         FragmentActivity activity = getActivity();
443         if (activity != null) {
444             activity.finish();
445         }
446     }
447 
448     @Override
onDestroy()449     public void onDestroy() {
450         super.onDestroy();
451         mWallpaperSetter.cleanUp();
452     }
453 
454     @Override
onSaveInstanceState(Bundle outState)455     public void onSaveInstanceState(Bundle outState) {
456         super.onSaveInstanceState(outState);
457 
458         final BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(mBottomSheet);
459         outState.putInt(KEY_BOTTOM_SHEET_STATE, bottomSheetBehavior.getState());
460     }
461 
onSetWallpaperClicked(View button)462     protected void onSetWallpaperClicked(View button) {
463         mWallpaperSetter.requestDestination(getContext(), getFragmentManager(), this,
464                 mWallpaper instanceof LiveWallpaperInfo);
465     }
466 
onExploreClicked(View button)467     private void onExploreClicked(View button) {
468         if (getContext() == null) {
469             return;
470         }
471         Context context = getContext();
472         mUserEventLogger.logActionClicked(mWallpaper.getCollectionId(context),
473                 mWallpaper.getActionLabelRes(context));
474 
475         startActivity(mExploreIntent);
476     }
477 
setUpBottomSheetListeners()478     private void setUpBottomSheetListeners() {
479         final BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(mBottomSheet);
480 
481         bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
482             @Override
483             public void onStateChanged(View bottomSheet, int newState) {
484                 // Don't respond to lingering state change events occurring after the fragment has
485                 // already been detached from the activity. Else, IllegalStateException may occur
486                 // when trying to fetch resources.
487                 if (getActivity() == null) {
488                     return;
489                 }
490                 switch (newState) {
491                     case STATE_COLLAPSED:
492                         setPreviewChecked(true /* checked */);
493                         break;
494                     case STATE_EXPANDED:
495                         setPreviewChecked(false /* checked */);
496                         break;
497                     default:
498                         Log.v(TAG, "Ignoring BottomSheet state: " + newState);
499                 }
500             }
501 
502             @Override
503             public void onSlide(View bottomSheet, float slideOffset) {
504                 float alpha;
505                 if (slideOffset >= 0) {
506                     alpha = slideOffset;
507                 } else {
508                     alpha = 1f - slideOffset;
509                 }
510                 setBottomSheetContentAlpha(alpha);
511             }
512         });
513     }
514 
setBottomSheetContentAlpha(float alpha)515     protected void setBottomSheetContentAlpha(float alpha) {
516 
517     }
518 
519     /**
520      * Sets current wallpaper to the device based on current zoom and scroll state.
521      *
522      * @param destination The wallpaper destination i.e. home vs. lockscreen vs. both.
523      */
setCurrentWallpaper(@estination int destination)524     protected abstract void setCurrentWallpaper(@Destination int destination);
525 
finishActivityWithResultOk()526     protected void finishActivityWithResultOk() {
527         Activity activity = requireActivity();
528         try {
529             Toast.makeText(activity,
530                     R.string.wallpaper_set_successfully_message, Toast.LENGTH_SHORT).show();
531         } catch (NotFoundException e) {
532             Log.e(TAG, "Could not show toast " + e);
533         }
534         activity.overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
535         activity.setResult(Activity.RESULT_OK);
536         activity.finish();
537     }
538 
showSetWallpaperErrorDialog(@estination int wallpaperDestination)539     protected void showSetWallpaperErrorDialog(@Destination int wallpaperDestination) {
540         SetWallpaperErrorDialogFragment newFragment = SetWallpaperErrorDialogFragment.newInstance(
541                 R.string.set_wallpaper_error_message, wallpaperDestination);
542         newFragment.setTargetFragment(this, UNUSED_REQUEST_CODE);
543 
544         // Show 'set wallpaper' error dialog now if it's safe to commit fragment transactions,
545         // otherwise stage it for later when the hosting activity is in a state to commit fragment
546         // transactions.
547         BasePreviewActivity activity = (BasePreviewActivity) requireActivity();
548         if (activity.isSafeToCommitFragmentTransaction()) {
549             newFragment.show(requireFragmentManager(), TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT);
550         } else {
551             mStagedSetWallpaperErrorDialogFragment = newFragment;
552         }
553     }
554 
555     /**
556      * Shows 'load wallpaper' error dialog now or stage it to be shown when the hosting activity is
557      * in a state that allows committing fragment transactions.
558      */
showLoadWallpaperErrorDialog()559     protected void showLoadWallpaperErrorDialog() {
560         LoadWallpaperErrorDialogFragment dialogFragment =
561                 LoadWallpaperErrorDialogFragment.newInstance();
562         dialogFragment.setTargetFragment(this, UNUSED_REQUEST_CODE);
563 
564         // Show 'load wallpaper' error dialog now or stage it to be shown when the hosting
565         // activity is in a state that allows committing fragment transactions.
566         BasePreviewActivity activity = (BasePreviewActivity) getActivity();
567         if (activity != null && activity.isSafeToCommitFragmentTransaction()) {
568             dialogFragment.show(requireFragmentManager(), TAG_LOAD_WALLPAPER_ERROR_DIALOG_FRAGMENT);
569         } else {
570             mStagedLoadWallpaperErrorDialogFragment = dialogFragment;
571         }
572     }
573 
574     /**
575      * Returns whether layout direction is RTL (or false for LTR). Since native RTL layout support
576      * was added in API 17, returns false for versions lower than 17.
577      */
isRtl()578     protected boolean isRtl() {
579         return getResources().getConfiguration().getLayoutDirection()
580                     == View.LAYOUT_DIRECTION_RTL;
581     }
582 
583     protected static class InfoPageController {
584 
createView(LayoutInflater inflater)585         public static View createView(LayoutInflater inflater) {
586             return inflater.inflate(R.layout.preview_page_info, null /* root */);
587         }
588 
589         private final int mPreviewMode;
590         private final View mInfoPage;
591         private final TextView mAttributionTitle;
592         private final TextView mAttributionSubtitle1;
593         private final TextView mAttributionSubtitle2;
594         private final Button mExploreButton;
595         private final Button mSetWallpaperButton;
596         private final View mSpacer;
597 
InfoPageController(View infoPage, int previewMode)598         public InfoPageController(View infoPage, int previewMode) {
599             mInfoPage = infoPage;
600             mPreviewMode = previewMode;
601 
602             mAttributionTitle = mInfoPage.findViewById(R.id.preview_attribution_pane_title);
603             mAttributionSubtitle1 = mInfoPage.findViewById(R.id.preview_attribution_pane_subtitle1);
604             mAttributionSubtitle2 = mInfoPage.findViewById(R.id.preview_attribution_pane_subtitle2);
605             mSpacer = mInfoPage.findViewById(R.id.spacer);
606 
607             mExploreButton = mInfoPage.findViewById(R.id.preview_attribution_pane_explore_button);
608             mSetWallpaperButton = mInfoPage.findViewById(
609                     R.id.preview_attribution_pane_set_wallpaper_button);
610         }
611 
populate(List<String> attributions, boolean showMetadata, OnClickListener setWallpaperOnClickListener, CharSequence exploreButtonLabel, @Nullable OnClickListener exploreOnClickListener)612         public void populate(List<String> attributions, boolean showMetadata,
613                 OnClickListener setWallpaperOnClickListener,
614                 CharSequence exploreButtonLabel,
615                 @Nullable OnClickListener exploreOnClickListener) {
616             if (attributions.size() > 0 && attributions.get(0) != null) {
617                 mAttributionTitle.setText(attributions.get(0));
618             }
619 
620             if (showMetadata) {
621                 if (attributions.size() > 1 && attributions.get(1) != null) {
622                     mAttributionSubtitle1.setVisibility(View.VISIBLE);
623                     mAttributionSubtitle1.setText(attributions.get(1));
624                 }
625 
626                 if (attributions.size() > 2 && attributions.get(2) != null) {
627                     mAttributionSubtitle2.setVisibility(View.VISIBLE);
628                     mAttributionSubtitle2.setText(attributions.get(2));
629                 }
630             }
631             setUpSetWallpaperButton(setWallpaperOnClickListener);
632 
633             setUpExploreButton(exploreButtonLabel, exploreOnClickListener);
634 
635             if (mExploreButton.getVisibility() == View.VISIBLE
636                     && mSetWallpaperButton.getVisibility() == View.VISIBLE) {
637                 mSpacer.setVisibility(View.VISIBLE);
638             } else {
639                 mSpacer.setVisibility(View.GONE);
640             }
641         }
642 
setContentAlpha(float alpha)643         public void setContentAlpha(float alpha) {
644             mSetWallpaperButton.setAlpha(alpha);
645             mExploreButton.setAlpha(alpha);
646             mAttributionTitle.setAlpha(alpha);
647             mAttributionSubtitle1.setAlpha(alpha);
648             mAttributionSubtitle2.setAlpha(alpha);
649         }
650 
setUpSetWallpaperButton(OnClickListener setWallpaperOnClickListener)651         private void setUpSetWallpaperButton(OnClickListener setWallpaperOnClickListener) {
652             if (mPreviewMode == MODE_VIEW_ONLY) {
653                 mSetWallpaperButton.setVisibility(View.GONE);
654             } else {
655                 mSetWallpaperButton.setVisibility(View.VISIBLE);
656                 mSetWallpaperButton.setOnClickListener(setWallpaperOnClickListener);
657             }
658         }
659 
setUpExploreButton(CharSequence label, @Nullable OnClickListener exploreOnClickListener)660         private void setUpExploreButton(CharSequence label,
661                 @Nullable OnClickListener exploreOnClickListener) {
662             mExploreButton.setVisibility(View.GONE);
663             if (exploreOnClickListener == null) {
664                 return;
665             }
666             mExploreButton.setVisibility(View.VISIBLE);
667             mExploreButton.setText(label);
668 
669             mExploreButton.setOnClickListener(exploreOnClickListener);
670         }
671     }
672 }
673