• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.android.wallpaper.widget.BottomActionBar.BottomAction.APPLY;
19 
20 import android.app.Activity;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.res.ColorStateList;
24 import android.content.res.Resources.NotFoundException;
25 import android.content.res.TypedArray;
26 import android.os.Bundle;
27 import android.util.Log;
28 import android.view.ContextThemeWrapper;
29 import android.view.LayoutInflater;
30 import android.view.View;
31 import android.view.ViewGroup;
32 import android.widget.Toast;
33 
34 import androidx.annotation.CallSuper;
35 import androidx.annotation.IdRes;
36 import androidx.annotation.IntDef;
37 import androidx.annotation.LayoutRes;
38 import androidx.annotation.Nullable;
39 import androidx.core.widget.ContentLoadingProgressBar;
40 import androidx.fragment.app.FragmentActivity;
41 
42 import com.android.wallpaper.R;
43 import com.android.wallpaper.model.LiveWallpaperInfo;
44 import com.android.wallpaper.model.WallpaperInfo;
45 import com.android.wallpaper.module.Injector;
46 import com.android.wallpaper.module.InjectorProvider;
47 import com.android.wallpaper.module.UserEventLogger;
48 import com.android.wallpaper.module.WallpaperPersister.Destination;
49 import com.android.wallpaper.module.WallpaperPreferences;
50 import com.android.wallpaper.module.WallpaperSetter;
51 import com.android.wallpaper.widget.BottomActionBar;
52 
53 import java.util.Date;
54 import java.util.List;
55 
56 /**
57  * Base Fragment to display the UI for previewing an individual wallpaper
58  */
59 public abstract class PreviewFragment extends AppbarFragment implements
60         SetWallpaperDialogFragment.Listener, SetWallpaperErrorDialogFragment.Listener,
61         LoadWallpaperErrorDialogFragment.Listener {
62 
63     /**
64      * User can view wallpaper and attributions in full screen, but "Set wallpaper" button is
65      * hidden.
66      */
67     static final int MODE_VIEW_ONLY = 0;
68 
69     /**
70      * User can view wallpaper and attributions in full screen and click "Set wallpaper" to set the
71      * wallpaper with pan and crop position to the device.
72      */
73     static final int MODE_CROP_AND_SET_WALLPAPER = 1;
74 
75     /**
76      * Possible preview modes for the fragment.
77      */
78     @IntDef({
79             MODE_VIEW_ONLY,
80             MODE_CROP_AND_SET_WALLPAPER})
81     public @interface PreviewMode {
82     }
83 
84     public static final String ARG_WALLPAPER = "wallpaper";
85     public static final String ARG_PREVIEW_MODE = "preview_mode";
86     public static final String ARG_VIEW_AS_HOME = "view_as_home";
87     public static final String ARG_TESTING_MODE_ENABLED = "testing_mode_enabled";
88 
89     /**
90      * Creates and returns new instance of {@link ImagePreviewFragment} with the provided wallpaper
91      * set as an argument.
92      */
newInstance(WallpaperInfo wallpaperInfo, @PreviewMode int mode, boolean viewAsHome, boolean testingModeEnabled)93     public static PreviewFragment newInstance(WallpaperInfo wallpaperInfo, @PreviewMode int mode,
94             boolean viewAsHome, boolean testingModeEnabled) {
95         Bundle args = new Bundle();
96         args.putParcelable(ARG_WALLPAPER, wallpaperInfo);
97         args.putInt(ARG_PREVIEW_MODE, mode);
98         args.putBoolean(ARG_VIEW_AS_HOME, viewAsHome);
99         args.putBoolean(ARG_TESTING_MODE_ENABLED, testingModeEnabled);
100 
101         PreviewFragment fragment = wallpaperInfo instanceof LiveWallpaperInfo
102                 ? new LivePreviewFragment() : new ImagePreviewFragment();
103         fragment.setArguments(args);
104         return fragment;
105     }
106 
107     private static final String TAG_LOAD_WALLPAPER_ERROR_DIALOG_FRAGMENT =
108             "load_wallpaper_error_dialog";
109     private static final String TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT =
110             "set_wallpaper_error_dialog";
111     private static final int UNUSED_REQUEST_CODE = 1;
112     private static final String TAG = "PreviewFragment";
113 
114     @PreviewMode
115     protected int mPreviewMode;
116 
117     protected boolean mViewAsHome;
118 
119     /**
120      * When true, enables a test mode of operation -- in which certain UI features are disabled to
121      * allow for UI tests to run correctly. Works around issue in ProgressDialog currently where the
122      * dialog constantly keeps the UI thread alive and blocks a test forever.
123      */
124     protected boolean mTestingModeEnabled;
125 
126     protected WallpaperInfo mWallpaper;
127     protected WallpaperSetter mWallpaperSetter;
128     protected UserEventLogger mUserEventLogger;
129     protected BottomActionBar mBottomActionBar;
130     protected ContentLoadingProgressBar mLoadingProgressBar;
131 
132     protected Intent mExploreIntent;
133     protected CharSequence mActionLabel;
134 
135     /**
136      * Staged error dialog fragments that were unable to be shown when the hosting activity didn't
137      * allow committing fragment transactions.
138      */
139     private SetWallpaperErrorDialogFragment mStagedSetWallpaperErrorDialogFragment;
140     private LoadWallpaperErrorDialogFragment mStagedLoadWallpaperErrorDialogFragment;
141 
getAttrColor(Context context, int attr)142     protected static int getAttrColor(Context context, int attr) {
143         TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
144         int colorAccent = ta.getColor(0, 0);
145         ta.recycle();
146         return colorAccent;
147     }
148 
149     @Override
onCreate(Bundle savedInstanceState)150     public void onCreate(Bundle savedInstanceState) {
151         super.onCreate(savedInstanceState);
152         Context appContext = getContext().getApplicationContext();
153         Injector injector = InjectorProvider.getInjector();
154 
155         mUserEventLogger = injector.getUserEventLogger(appContext);
156         mWallpaper = getArguments().getParcelable(ARG_WALLPAPER);
157 
158         //noinspection ResourceType
159         mPreviewMode = getArguments().getInt(ARG_PREVIEW_MODE);
160         mViewAsHome = getArguments().getBoolean(ARG_VIEW_AS_HOME);
161         mTestingModeEnabled = getArguments().getBoolean(ARG_TESTING_MODE_ENABLED);
162         mWallpaperSetter = new WallpaperSetter(injector.getWallpaperPersister(appContext),
163                 injector.getPreferences(appContext), mUserEventLogger, mTestingModeEnabled);
164 
165         setHasOptionsMenu(true);
166 
167         Activity activity = getActivity();
168         List<String> attributions = getAttributions(activity);
169         if (attributions.size() > 0 && attributions.get(0) != null) {
170             activity.setTitle(attributions.get(0));
171         }
172     }
173 
174     @Override
175     @CallSuper
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)176     public View onCreateView(LayoutInflater inflater, ViewGroup container,
177                              Bundle savedInstanceState) {
178         View view = inflater.inflate(getLayoutResId(), container, false);
179         setUpToolbar(view);
180 
181         mLoadingProgressBar = view.findViewById(getLoadingIndicatorResId());
182         mLoadingProgressBar.show();
183         return view;
184     }
185 
186     @Override
onBottomActionBarReady(BottomActionBar bottomActionBar)187     protected void onBottomActionBarReady(BottomActionBar bottomActionBar) {
188         mBottomActionBar = bottomActionBar;
189         // TODO: Extract the common code here.
190     }
191 
getAttributions(Context context)192     protected List<String> getAttributions(Context context) {
193         return mWallpaper.getAttributions(context);
194     }
195 
196     @LayoutRes
getLayoutResId()197     protected abstract int getLayoutResId();
198 
199     @IdRes
getLoadingIndicatorResId()200     protected abstract int getLoadingIndicatorResId();
201 
getDeviceDefaultTheme()202     protected int getDeviceDefaultTheme() {
203         return android.R.style.Theme_DeviceDefault;
204     }
205 
206     @Override
onResume()207     public void onResume() {
208         super.onResume();
209 
210         WallpaperPreferences preferences =
211                 InjectorProvider.getInjector().getPreferences(getActivity());
212         preferences.setLastAppActiveTimestamp(new Date().getTime());
213 
214         // Show the staged 'load wallpaper' or 'set wallpaper' error dialog fragments if there is
215         // one that was unable to be shown earlier when this fragment's hosting activity didn't
216         // allow committing fragment transactions.
217         if (mStagedLoadWallpaperErrorDialogFragment != null) {
218             mStagedLoadWallpaperErrorDialogFragment.show(
219                     requireFragmentManager(), TAG_LOAD_WALLPAPER_ERROR_DIALOG_FRAGMENT);
220             mStagedLoadWallpaperErrorDialogFragment = null;
221         }
222         if (mStagedSetWallpaperErrorDialogFragment != null) {
223             mStagedSetWallpaperErrorDialogFragment.show(
224                     requireFragmentManager(), TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT);
225             mStagedSetWallpaperErrorDialogFragment = null;
226         }
227     }
228 
setUpExploreIntentAndLabel(@ullable Runnable callback)229     protected void setUpExploreIntentAndLabel(@Nullable Runnable callback) {
230         Context context = getContext();
231         if (context == null) {
232             return;
233         }
234 
235         WallpaperInfoHelper.loadExploreIntent(context, mWallpaper,
236                 (actionLabel, exploreIntent) -> {
237                     mActionLabel = actionLabel;
238                     mExploreIntent = exploreIntent;
239                     if (callback != null) {
240                         callback.run();
241                     }
242                 }
243         );
244     }
245 
246     /**
247      * Configure loading indicator with a MaterialProgressDrawable.
248      */
setUpLoadingIndicator()249     protected void setUpLoadingIndicator() {
250         mLoadingProgressBar.setProgressTintList(ColorStateList.valueOf(getAttrColor(
251                 new ContextThemeWrapper(requireContext(), getDeviceDefaultTheme()),
252                 android.R.attr.colorAccent)));
253         mLoadingProgressBar.show();
254     }
255 
isLoaded()256     protected abstract boolean isLoaded();
257 
258     @Override
onSet(int destination)259     public void onSet(int destination) {
260         setCurrentWallpaper(destination);
261     }
262 
263     @Override
onDialogDismissed(boolean withItemSelected)264     public void onDialogDismissed(boolean withItemSelected) {
265         mBottomActionBar.deselectAction(APPLY);
266     }
267 
268     @Override
onClickTryAgain(@estination int wallpaperDestination)269     public void onClickTryAgain(@Destination int wallpaperDestination) {
270         setCurrentWallpaper(wallpaperDestination);
271     }
272 
273     @Override
onClickOk()274     public void onClickOk() {
275         FragmentActivity activity = getActivity();
276         if (activity != null) {
277             activity.finish();
278         }
279     }
280 
281     @Override
onDestroy()282     public void onDestroy() {
283         super.onDestroy();
284         mWallpaperSetter.cleanUp();
285     }
286 
287     @Override
getDefaultTitle()288     public CharSequence getDefaultTitle() {
289         return getContext().getString(R.string.preview);
290     }
291 
onSetWallpaperClicked(View button)292     protected void onSetWallpaperClicked(View button) {
293         mWallpaperSetter.requestDestination(getActivity(), getFragmentManager(), this,
294                 mWallpaper instanceof LiveWallpaperInfo);
295     }
296 
onExploreClicked(View button)297     protected void onExploreClicked(View button) {
298         if (getContext() == null) {
299             return;
300         }
301         Context context = getContext();
302         mUserEventLogger.logActionClicked(mWallpaper.getCollectionId(context),
303                 mWallpaper.getActionLabelRes(context));
304 
305         startActivity(mExploreIntent);
306     }
307 
308     /**
309      * Sets current wallpaper to the device based on current zoom and scroll state.
310      *
311      * @param destination The wallpaper destination i.e. home vs. lockscreen vs. both.
312      */
setCurrentWallpaper(@estination int destination)313     protected abstract void setCurrentWallpaper(@Destination int destination);
314 
finishActivity(boolean success)315     protected void finishActivity(boolean success) {
316         Activity activity = getActivity();
317         if (activity == null) {
318             return;
319         }
320         if (success) {
321             try {
322                 Toast.makeText(activity,
323                         R.string.wallpaper_set_successfully_message, Toast.LENGTH_SHORT).show();
324             } catch (NotFoundException e) {
325                 Log.e(TAG, "Could not show toast " + e);
326             }
327             activity.setResult(Activity.RESULT_OK);
328         }
329         activity.overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
330         activity.finish();
331     }
332 
showSetWallpaperErrorDialog(@estination int wallpaperDestination)333     protected void showSetWallpaperErrorDialog(@Destination int wallpaperDestination) {
334         SetWallpaperErrorDialogFragment newFragment = SetWallpaperErrorDialogFragment.newInstance(
335                 R.string.set_wallpaper_error_message, wallpaperDestination);
336         newFragment.setTargetFragment(this, UNUSED_REQUEST_CODE);
337 
338         // Show 'set wallpaper' error dialog now if it's safe to commit fragment transactions,
339         // otherwise stage it for later when the hosting activity is in a state to commit fragment
340         // transactions.
341         BasePreviewActivity activity = (BasePreviewActivity) requireActivity();
342         if (activity.isSafeToCommitFragmentTransaction()) {
343             newFragment.show(requireFragmentManager(), TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT);
344         } else {
345             mStagedSetWallpaperErrorDialogFragment = newFragment;
346         }
347     }
348 
349     /**
350      * Shows 'load wallpaper' error dialog now or stage it to be shown when the hosting activity is
351      * in a state that allows committing fragment transactions.
352      */
showLoadWallpaperErrorDialog()353     protected void showLoadWallpaperErrorDialog() {
354         LoadWallpaperErrorDialogFragment dialogFragment =
355                 LoadWallpaperErrorDialogFragment.newInstance();
356         dialogFragment.setTargetFragment(this, UNUSED_REQUEST_CODE);
357 
358         // Show 'load wallpaper' error dialog now or stage it to be shown when the hosting
359         // activity is in a state that allows committing fragment transactions.
360         BasePreviewActivity activity = (BasePreviewActivity) getActivity();
361         if (activity != null && activity.isSafeToCommitFragmentTransaction()) {
362             dialogFragment.show(requireFragmentManager(), TAG_LOAD_WALLPAPER_ERROR_DIALOG_FRAGMENT);
363         } else {
364             mStagedLoadWallpaperErrorDialogFragment = dialogFragment;
365         }
366     }
367 
368     /**
369      * Returns whether layout direction is RTL (or false for LTR). Since native RTL layout support
370      * was added in API 17, returns false for versions lower than 17.
371      */
isRtl()372     protected boolean isRtl() {
373         return getResources().getConfiguration().getLayoutDirection()
374                     == View.LAYOUT_DIRECTION_RTL;
375     }
376 }
377