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