blob: 85f1a6a3e79869ecec33757dfded678f37e5786d [file] [log] [blame]
Jon Miranda16ea1b12017-12-12 14:52:48 -08001/*
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 */
16package com.android.wallpaper.picker;
17
18import android.Manifest.permission;
19import android.app.Activity;
20import android.app.ProgressDialog;
21import android.app.WallpaperManager;
22import android.content.Context;
23import android.content.Intent;
24import android.content.pm.PackageManager;
25import android.graphics.Color;
26import android.graphics.Point;
27import android.graphics.PorterDuff.Mode;
28import android.graphics.drawable.Drawable;
29import android.net.Uri;
30import android.os.AsyncTask;
31import android.os.Build.VERSION;
32import android.os.Build.VERSION_CODES;
33import android.os.Bundle;
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -070034import android.service.wallpaper.WallpaperService;
Jon Miranda16ea1b12017-12-12 14:52:48 -080035import android.support.annotation.NonNull;
36import android.support.annotation.Nullable;
37import android.support.design.widget.BottomSheetBehavior;
38import android.support.design.widget.BottomSheetBehavior.BottomSheetCallback;
39import android.support.design.widget.TabLayout;
40import android.support.design.widget.TabLayout.OnTabSelectedListener;
41import android.support.design.widget.TabLayout.Tab;
42import android.support.v4.app.Fragment;
43import android.support.v4.app.FragmentManager;
44import android.support.v7.app.AlertDialog;
45import android.support.v7.widget.Toolbar;
46import android.util.Log;
47import android.view.View;
48import android.view.View.OnClickListener;
49import android.widget.Button;
50import android.widget.FrameLayout;
51import android.widget.ImageView;
52import android.widget.LinearLayout;
53import android.widget.TextView;
54
55import com.android.wallpaper.R;
56import com.android.wallpaper.asset.Asset;
57import com.android.wallpaper.compat.ButtonDrawableSetterCompat;
58import com.android.wallpaper.compat.WallpaperManagerCompat;
59import com.android.wallpaper.config.Flags;
60import com.android.wallpaper.model.Category;
61import com.android.wallpaper.model.CategoryProvider;
62import com.android.wallpaper.model.CategoryReceiver;
63import com.android.wallpaper.model.ImageWallpaperInfo;
64import com.android.wallpaper.model.InlinePreviewIntentFactory;
65import com.android.wallpaper.model.WallpaperInfo;
66import com.android.wallpaper.module.CurrentWallpaperInfoFactory;
67import com.android.wallpaper.module.CurrentWallpaperInfoFactory.WallpaperInfoCallback;
68import com.android.wallpaper.module.DailyLoggingAlarmScheduler;
69import com.android.wallpaper.module.ExploreIntentChecker;
70import com.android.wallpaper.module.FormFactorChecker;
71import com.android.wallpaper.module.FormFactorChecker.FormFactor;
72import com.android.wallpaper.module.Injector;
73import com.android.wallpaper.module.InjectorProvider;
74import com.android.wallpaper.module.NetworkStatusNotifier;
75import com.android.wallpaper.module.NetworkStatusNotifier.NetworkStatus;
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -070076import com.android.wallpaper.module.PackageStatusNotifier;
Jon Miranda16ea1b12017-12-12 14:52:48 -080077import com.android.wallpaper.module.UserEventLogger;
78import com.android.wallpaper.module.UserEventLogger.WallpaperSetFailureReason;
79import com.android.wallpaper.module.WallpaperPersister;
80import com.android.wallpaper.module.WallpaperPersister.Destination;
81import com.android.wallpaper.module.WallpaperPersister.SetWallpaperCallback;
82import com.android.wallpaper.module.WallpaperPersister.WallpaperPosition;
83import com.android.wallpaper.module.WallpaperPreferences;
84import com.android.wallpaper.module.WallpaperPreferences.PresentationMode;
85import com.android.wallpaper.module.WallpaperRotationRefresher;
86import com.android.wallpaper.module.WallpaperRotationRefresher.Listener;
87import com.android.wallpaper.picker.PreviewActivity.PreviewActivityIntentFactory;
88import com.android.wallpaper.picker.ViewOnlyPreviewActivity.ViewOnlyPreviewActivityIntentFactory;
89import com.android.wallpaper.picker.WallpaperDisabledFragment.WallpaperSupportLevel;
90import com.android.wallpaper.picker.individual.IndividualPickerActivity.IndividualPickerActivityIntentFactory;
91import com.android.wallpaper.picker.individual.IndividualPickerFragment;
92import com.android.wallpaper.util.ScreenSizeCalculator;
93import com.android.wallpaper.util.ThrowableAnalyzer;
94
95import java.util.ArrayList;
96import java.util.List;
97
98/**
99 * Activity allowing users to select a category of wallpapers to choose from.
100 */
101public class TopLevelPickerActivity extends BaseActivity implements WallpapersUiContainer,
102 CurrentWallpaperBottomSheetPresenter, SetWallpaperErrorDialogFragment.Listener,
103 MyPhotosLauncher {
104 private static final int SHOW_CATEGORY_REQUEST_CODE = 0;
105 private static final int PREVIEW_WALLPAPER_REQUEST_CODE = 1;
106 private static final int VIEW_ONLY_PREVIEW_WALLPAPER_REQUEST_CODE = 2;
107 private static final int READ_EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE = 3;
108
109 private static final String TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT =
110 "toplevel_set_wallpaper_error_dialog";
111
112 private static final String TAG = "TopLevelPicker";
113 private static final String KEY_SELECTED_CATEGORY_TAB = "selected_category_tab";
114
115 private IndividualPickerActivityIntentFactory mPickerIntentFactory;
116 private InlinePreviewIntentFactory mPreviewIntentFactory;
117 private InlinePreviewIntentFactory mViewOnlyPreviewIntentFactory;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800118 private int mLastSelectedCategoryTabIndex;
119 @FormFactor
120 private int mFormFactor;
121 private WallpaperPreferences mPreferences;
122 private UserEventLogger mUserEventLogger;
123 private NetworkStatusNotifier mNetworkStatusNotifier;
124 private NetworkStatusNotifier.Listener mNetworkStatusListener;
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700125 private PackageStatusNotifier mPackageStatusNotifier;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800126 private WallpaperPersister mWallpaperPersister;
127 private boolean mWasCustomPhotoWallpaperSet;
128 @WallpaperPosition
129 private int mCustomPhotoWallpaperPosition;
130
131 /**
132 * Progress dialogs for "refresh daily wallpaper" and "set wallpaper" operations.
133 */
134 private ProgressDialog mRefreshWallpaperProgressDialog;
135 private ProgressDialog mSetWallpaperProgressDialog;
136
137 /**
138 * Designates a test mode of operation -- in which certain UI features are disabled to allow for
139 * UI tests to run correctly.
140 */
141 private boolean mTestingMode;
142
143 /**
144 * UI for the "currently set wallpaper" BottomSheet.
145 */
146 private LinearLayout mBottomSheet;
147 private ImageView mCurrentWallpaperImage;
148 private TextView mCurrentWallpaperPresentationMode;
149 private TextView mCurrentWallpaperTitle;
150 private TextView mCurrentWallpaperSubtitle;
151 private Button mCurrentWallpaperExploreButton;
152 private Button mCurrentWallpaperSkipWallpaperButton;
153 private FrameLayout mFragmentContainer;
154 private FrameLayout mLoadingIndicatorContainer;
155 private LinearLayout mWallpaperPositionOptions;
156
157 private List<PermissionChangedListener> mPermissionChangedListeners;
158
159 /**
160 * Staged error dialog fragments that were unable to be shown when the activity didn't allow
161 * committing fragment transactions.
162 */
163 private SetWallpaperErrorDialogFragment mStagedSetWallpaperErrorDialogFragment;
164
165 /**
166 * A wallpaper pending set to the device--we retain a reference to this in order to facilitate
167 * retry or re-crop operations.
168 */
169 private WallpaperInfo mPendingSetWallpaperInfo;
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700170 private PackageStatusNotifier.Listener mLiveWallpaperStatusListener;
171 private PackageStatusNotifier.Listener mThirdPartyStatusListener;
172 private CategoryProvider mCategoryProvider;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800173
174 private static int getTextColorIdForWallpaperPositionButton(boolean isSelected) {
175 return isSelected ? R.color.accent_color : R.color.material_grey500;
176 }
177
178 @Override
179 protected void onCreate(Bundle savedInstanceState) {
180 super.onCreate(savedInstanceState);
181
182 mPickerIntentFactory = new IndividualPickerActivityIntentFactory();
183 mPreviewIntentFactory = new PreviewActivityIntentFactory();
184 mViewOnlyPreviewIntentFactory = new ViewOnlyPreviewActivityIntentFactory();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800185 mLastSelectedCategoryTabIndex = -1;
186
187 Injector injector = InjectorProvider.getInjector();
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700188 mCategoryProvider = injector.getCategoryProvider(this);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800189 mPreferences = injector.getPreferences(this);
190 mUserEventLogger = injector.getUserEventLogger(this);
191 mNetworkStatusNotifier = injector.getNetworkStatusNotifier(this);
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700192 mPackageStatusNotifier = injector.getPackageStatusNotifier(this);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800193 final FormFactorChecker formFactorChecker = injector.getFormFactorChecker(this);
194 mFormFactor = formFactorChecker.getFormFactor();
195 mWallpaperPersister = injector.getWallpaperPersister(this);
196 mWasCustomPhotoWallpaperSet = false;
197
198 mPermissionChangedListeners = new ArrayList<>();
199
200 @WallpaperSupportLevel int wallpaperSupportLevel = getWallpaperSupportLevel();
201 if (wallpaperSupportLevel != WallpaperDisabledFragment.SUPPORTED_CAN_SET) {
202 setContentView(R.layout.activity_single_fragment);
203
204 FragmentManager fm = getSupportFragmentManager();
205 WallpaperDisabledFragment wallpaperDisabledFragment =
206 WallpaperDisabledFragment.newInstance(wallpaperSupportLevel);
207 fm.beginTransaction()
208 .add(R.id.fragment_container, wallpaperDisabledFragment)
209 .commit();
210 return;
211 }
212
213 if (mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE) {
214 initializeMobile();
215 } else { // DESKTOP
216 initializeDesktop(savedInstanceState);
217 }
218 }
219
220 @Override
221 protected void onResume() {
222 super.onResume();
223
224 // Show the staged 'load wallpaper' or 'set wallpaper' error dialog fragments if there is one
225 // that was unable to be shown earlier when this fragment's hosting activity didn't allow
226 // committing fragment transactions.
227 if (mStagedSetWallpaperErrorDialogFragment != null) {
228 mStagedSetWallpaperErrorDialogFragment.show(
229 getSupportFragmentManager(), TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT);
230 mStagedSetWallpaperErrorDialogFragment = null;
231 }
232 }
233
234 @Override
235 protected void onDestroy() {
236 super.onDestroy();
237
238 if (mNetworkStatusListener != null) {
239 mNetworkStatusNotifier.unregisterListener(mNetworkStatusListener);
240 }
241
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700242 if (mPackageStatusNotifier != null) {
243 mPackageStatusNotifier.removeListener(mLiveWallpaperStatusListener);
244 mPackageStatusNotifier.removeListener(mThirdPartyStatusListener);
245 }
246
Jon Miranda16ea1b12017-12-12 14:52:48 -0800247 if (mRefreshWallpaperProgressDialog != null) {
248 mRefreshWallpaperProgressDialog.dismiss();
249 }
250 if (mSetWallpaperProgressDialog != null) {
251 mSetWallpaperProgressDialog.dismiss();
252 }
253
254 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP && mWasCustomPhotoWallpaperSet) {
255 mUserEventLogger.logWallpaperPosition(mCustomPhotoWallpaperPosition);
256 }
257 }
258
259 @Override
260 public void requestCustomPhotoPicker(PermissionChangedListener listener) {
261 if (!isReadExternalStoragePermissionGranted()) {
262 PermissionChangedListener wrappedListener = new PermissionChangedListener() {
263 @Override
264 public void onPermissionsGranted() {
265 listener.onPermissionsGranted();
266 showCustomPhotoPicker();
267 }
268
269 @Override
270 public void onPermissionsDenied(boolean dontAskAgain) {
271 listener.onPermissionsDenied(dontAskAgain);
272 }
273 };
274 requestExternalStoragePermission(wrappedListener);
275
276 return;
277 }
278
279 showCustomPhotoPicker();
280 }
281
282 void requestExternalStoragePermission(PermissionChangedListener listener) {
283 mPermissionChangedListeners.add(listener);
284 requestPermissions(
285 new String[]{permission.READ_EXTERNAL_STORAGE},
286 READ_EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE);
287 }
288
289 /**
290 * Returns whether READ_EXTERNAL_STORAGE has been granted for the application.
291 */
292 boolean isReadExternalStoragePermissionGranted() {
293 return getPackageManager().checkPermission(permission.READ_EXTERNAL_STORAGE,
294 getPackageName()) == PackageManager.PERMISSION_GRANTED;
295 }
296
297 private void showCustomPhotoPicker() {
298 Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
299 intent.setType("image/*");
300 startActivityForResult(intent, SHOW_CATEGORY_REQUEST_CODE);
301 }
302
303 private void initializeMobile() {
304 setContentView(R.layout.activity_single_fragment_with_toolbar);
305
306 // Set toolbar as the action bar.
307 Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
308 setSupportActionBar(toolbar);
309
310 FragmentManager fm = getSupportFragmentManager();
311 Fragment fragment = fm.findFragmentById(R.id.fragment_container);
312
313 boolean forceCategoryRefresh = false;
314 if (fragment == null) {
315 // App launch specific logic: log the "app launched" event and set up daily logging.
316 mUserEventLogger.logAppLaunched();
317 DailyLoggingAlarmScheduler.setAlarm(getApplicationContext());
318
319 CategoryPickerFragment newFragment = new CategoryPickerFragment();
320 fm.beginTransaction()
321 .add(R.id.fragment_container, newFragment)
322 .commit();
323
324 forceCategoryRefresh = true;
325 }
326
327 populateCategories(-1, forceCategoryRefresh);
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700328 mLiveWallpaperStatusListener = this::updateLiveWallpapersCategories;
329 mThirdPartyStatusListener = this::updateThirdPartyCategories;
330 mPackageStatusNotifier.addListener(mLiveWallpaperStatusListener,
331 WallpaperService.SERVICE_INTERFACE);
332 mPackageStatusNotifier.addListener(mThirdPartyStatusListener,
333 Intent.ACTION_SET_WALLPAPER);
334 }
335
336 private void updateThirdPartyCategories(String packageName, @PackageStatusNotifier.PackageStatus
337 int status) {
338
339 if (status == PackageStatusNotifier.PackageStatus.ADDED) {
340 mCategoryProvider.fetchCategories(new CategoryReceiver() {
341 @Override
342 public void onCategoryReceived(Category category) {
343 if (category.supportsThirdParty() && category.containsThirdParty(packageName)) {
344 addCategory(category, false);
345 }
346 }
347
348 @Override
349 public void doneFetchingCategories() {
350 // Do nothing here.
351 }
352 }, true);
353 } else if (status == PackageStatusNotifier.PackageStatus.REMOVED) {
354 Category oldCategory = findThirdPartyCategory(packageName);
355 if (oldCategory != null) {
356 mCategoryProvider.fetchCategories(new CategoryReceiver() {
357 @Override
358 public void onCategoryReceived(Category category) {
359 // Do nothing here
360 }
361
362 @Override
363 public void doneFetchingCategories() {
364 removeCategory(oldCategory);
365 }
366 }, true);
367 }
368 } else {
369 // CHANGED package, let's reload all categories as we could have more or fewer now
370 populateCategories(-1, true);
371 }
372 }
373
374 private Category findThirdPartyCategory(String packageName) {
375 int size = mCategoryProvider.getSize();
376 for (int i = 0; i < size; i++) {
377 Category category = mCategoryProvider.getCategory(i);
378 if (category.supportsThirdParty() && category.containsThirdParty(packageName)) {
379 return category;
380 }
381 }
382 return null;
383 }
384
385 private void updateLiveWallpapersCategories(String packageName,
386 @PackageStatusNotifier.PackageStatus int status) {
387 String liveWallpaperCollectionId = getString(R.string.live_wallpaper_collection_id);
388 Category oldLiveWallpapersCategory = mCategoryProvider.getCategory(
389 liveWallpaperCollectionId);
390 if (status == PackageStatusNotifier.PackageStatus.REMOVED
391 && (oldLiveWallpapersCategory == null
392 || !oldLiveWallpapersCategory.containsThirdParty(packageName))) {
393 // If we're removing a wallpaper and the live category didn't contain it already,
394 // there's nothing to do.
395 return;
396 }
397 mCategoryProvider.fetchCategories(new CategoryReceiver() {
398 @Override
399 public void onCategoryReceived(Category category) {
400 // Do nothing here
401 }
402
403 @Override
404 public void doneFetchingCategories() {
405 Category liveWallpapersCategory =
406 mCategoryProvider.getCategory(liveWallpaperCollectionId);
407 if (liveWallpapersCategory == null) {
408 // There are no more 3rd party live wallpapers, so the Category is gone.
409 removeCategory(oldLiveWallpapersCategory);
410 } else {
411 if (oldLiveWallpapersCategory != null) {
412 updateCategory(liveWallpapersCategory);
413 } else {
414 addCategory(liveWallpapersCategory, false);
415 }
416 }
417 }
418 }, true);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800419 }
420
421 private void initializeDesktop(Bundle savedInstanceState) {
422 setContentView(R.layout.activity_top_level_desktop);
423
424 mBottomSheet = (LinearLayout) findViewById(R.id.bottom_sheet);
425 mCurrentWallpaperImage = (ImageView) mBottomSheet.findViewById(R.id.current_wallpaper_image);
426 mCurrentWallpaperImage.getLayoutParams().width = getSingleWallpaperImageWidthPx();
427
428 mCurrentWallpaperPresentationMode =
429 (TextView) mBottomSheet.findViewById(R.id.current_wallpaper_presentation_mode);
430 mCurrentWallpaperTitle = (TextView) findViewById(R.id.current_wallpaper_title);
431 mCurrentWallpaperSubtitle = (TextView) findViewById(R.id.current_wallpaper_subtitle);
432 mCurrentWallpaperExploreButton = (Button) findViewById(
433 R.id.current_wallpaper_explore_button);
434 mCurrentWallpaperSkipWallpaperButton = (Button) findViewById(
435 R.id.current_wallpaper_skip_wallpaper_button);
436 mFragmentContainer = (FrameLayout) findViewById(R.id.fragment_container);
437 mLoadingIndicatorContainer = (FrameLayout) findViewById(R.id.loading_indicator_container);
438 mWallpaperPositionOptions = (LinearLayout) findViewById(
439 R.id.desktop_wallpaper_position_options);
440
441 final TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout);
442 tabLayout.addOnTabSelectedListener(new OnTabSelectedListener() {
443 @Override
444 public void onTabSelected(Tab tab) {
445 Category category = (Category) tab.getTag();
446 show(category.getCollectionId());
447 mLastSelectedCategoryTabIndex = tabLayout.getSelectedTabPosition();
448 }
449
450 @Override
451 public void onTabUnselected(Tab tab) {
452 }
453
454 @Override
455 public void onTabReselected(Tab tab) {
456 Category category = (Category) tab.getTag();
457 // If offline, "My photos" may be the only visible category. In this case we want to allow
458 // re-selection so user can still select a photo as wallpaper while offline.
459 if (!category.isEnumerable()) {
460 onTabSelected(tab);
461 }
462 }
463 });
464
465 FragmentManager fm = getSupportFragmentManager();
466 Fragment fragment = fm.findFragmentById(R.id.fragment_container);
467
468 if (fragment == null) {
469 // App launch specific logic: log the "app launched" event and set up daily logging.
470 mUserEventLogger.logAppLaunched();
471 DailyLoggingAlarmScheduler.setAlarm(getApplicationContext());
472 }
473
474 mNetworkStatusListener = new NetworkStatusNotifier.Listener() {
475 @Override
476 public void onNetworkChanged(@NetworkStatus int networkStatus) {
477 initializeDesktopBasedOnNetwork(networkStatus, savedInstanceState);
478 }
479 };
480 // Upon registering a listener, the onNetworkChanged method is immediately called with the
481 // initial network status.
482 mNetworkStatusNotifier.registerListener(mNetworkStatusListener);
483 }
484
485 private void initializeDesktopBasedOnNetwork(@NetworkStatus int networkStatus,
486 Bundle savedInstanceState) {
487 if (networkStatus == NetworkStatusNotifier.NETWORK_CONNECTED) {
488 initializeDesktopOnline(savedInstanceState);
489 } else {
490 initializeDesktopOffline();
491 }
492 }
493
494 private void initializeDesktopOnline(Bundle savedInstanceState) {
495 FragmentManager fm = getSupportFragmentManager();
496 Fragment fragment = fm.findFragmentById(R.id.fragment_container);
497
498 // Require a category refresh if this is the first load of the app or if the app is now
499 // returning online after having been offline.
500 boolean forceCategoryRefresh = fragment == null || fragment instanceof OfflineDesktopFragment;
501
502 if (fragment != null) {
503 fm.beginTransaction()
504 .remove(fragment)
505 .commit();
506 }
507
508 int selectedTabPosition = savedInstanceState != null
509 ? savedInstanceState.getInt(KEY_SELECTED_CATEGORY_TAB) : -1;
510 populateCategories(selectedTabPosition, forceCategoryRefresh);
511
512 setDesktopLoading(true);
513 setUpBottomSheet();
514 refreshCurrentWallpapers(null /* refreshListener */);
515 }
516
517 private void initializeDesktopOffline() {
518 FragmentManager fm = getSupportFragmentManager();
519 Fragment fragment = fm.findFragmentById(R.id.fragment_container);
520
521 if (fragment != null) {
522 fm.beginTransaction()
523 .remove(fragment)
524 .commit();
525 }
526 OfflineDesktopFragment newFragment = new OfflineDesktopFragment();
527 fm.beginTransaction()
528 .add(R.id.fragment_container, newFragment)
529 .commit();
530
531 // Reset the last selected category tab index to ensure the app doesn't try to reselect a tab
532 // for a category not yet repopulated.
533 mLastSelectedCategoryTabIndex = -1;
534
535 populateCategories(-1 /* selectedTabPosition */, true /* forceCategoryRefresh */);
536
537 setDesktopLoading(false);
538 setCurrentWallpapersExpanded(false);
539 }
540
541 /**
542 * Sets the status of the loading indicator overlay in desktop mode.
543 *
544 * @param loading Whether an indeterminate loading indicator is displayed in place of the main
545 * fragment.
546 */
547 private void setDesktopLoading(boolean loading) {
548 if (loading) {
549 mLoadingIndicatorContainer.setVisibility(View.VISIBLE);
550 mFragmentContainer.setVisibility(View.GONE);
551 } else {
552 mLoadingIndicatorContainer.setVisibility(View.GONE);
553 mFragmentContainer.setVisibility(View.VISIBLE);
554 }
555 }
556
557 /**
558 * Returns the width (in physical px) to use for the "currently set wallpaper" thumbnail.
559 */
560 private int getSingleWallpaperImageWidthPx() {
561 Point screenSize = ScreenSizeCalculator.getInstance().getScreenSize(
562 getWindowManager().getDefaultDisplay());
563
564 int height = getResources().getDimensionPixelSize(
565 R.dimen.current_wallpaper_bottom_sheet_thumb_height);
566 return height * screenSize.x / screenSize.y;
567 }
568
569 /**
570 * Enables and populates the "Currently set" wallpaper BottomSheet.
571 */
572 private void setUpBottomSheet() {
573 mBottomSheet.setVisibility(View.VISIBLE);
574
Jon Miranda16ea1b12017-12-12 14:52:48 -0800575 if (Flags.skipDailyWallpaperButtonEnabled) {
576 // Add "next" icon to the Next Wallpaper button
577 Drawable nextWallpaperButtonDrawable = getResources().getDrawable(
Santiago Etchebehered24506c2018-04-05 17:02:42 -0700578 R.drawable.ic_refresh_18px);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800579
580 // This Drawable's state is shared across the app, so make a copy of it before applying a
581 // color tint as not to affect other clients elsewhere in the app.
582 nextWallpaperButtonDrawable =
583 nextWallpaperButtonDrawable.getConstantState().newDrawable().mutate();
584 // Color the "compass" icon with the accent color.
585 nextWallpaperButtonDrawable.setColorFilter(
586 getResources().getColor(R.color.accent_color), Mode.SRC_IN);
587 ButtonDrawableSetterCompat.setDrawableToButtonStart(
588 mCurrentWallpaperSkipWallpaperButton, nextWallpaperButtonDrawable);
589 }
590
591 final BottomSheetBehavior<LinearLayout> bottomSheetBehavior =
592 BottomSheetBehavior.from(mBottomSheet);
593 bottomSheetBehavior.setBottomSheetCallback(new BottomSheetCallback() {
594 @Override
595 public void onStateChanged(@NonNull View view, int i) {
596 }
597
598 @Override
599 public void onSlide(@NonNull View view, float slideOffset) {
600 float alpha;
601 if (slideOffset >= 0) {
602 alpha = slideOffset;
603 } else {
604 alpha = 1f - slideOffset;
605 }
606 LinearLayout bottomSheetContents = (LinearLayout) findViewById(R.id.bottom_sheet_contents);
607 bottomSheetContents.setAlpha(alpha);
608 }
609 });
610 }
611
612 /**
613 * Enables a test mode of operation -- in which certain UI features are disabled to allow for
614 * UI tests to run correctly. Works around issue in ProgressDialog currently where the dialog
615 * constantly keeps the UI thread alive and blocks a test forever.
616 */
617 void setTestingMode(boolean testingMode) {
618 mTestingMode = testingMode;
619 }
620
621 /**
622 * Obtains the {@link WallpaperInfo} object(s) representing the wallpaper(s) currently set to the
623 * device from the {@link CurrentWallpaperInfoFactory} and displays them in the BottomSheet.
624 */
625 @Override
626 public void refreshCurrentWallpapers(@Nullable RefreshListener refreshListener) {
627 final Injector injector = InjectorProvider.getInjector();
628 final Context appContext = getApplicationContext();
629
630 CurrentWallpaperInfoFactory factory = injector.getCurrentWallpaperFactory(this);
631 factory.createCurrentWallpaperInfos(new WallpaperInfoCallback() {
632 @Override
633 public void onWallpaperInfoCreated(
634 final WallpaperInfo homeWallpaper,
635 @Nullable final WallpaperInfo lockWallpaper,
636 @PresentationMode final int presentationMode) {
637
638 if (isDestroyed()) {
639 return;
640 }
641
642 // Fetch the home wallpaper's thumbnail asset asynchronously to work around expensive
643 // method call to WallpaperManager#getWallpaperFile made from the CurrentWallpaperInfoVN
644 // getAsset() method.
645 AssetReceiver assetReceiver = (Asset thumbAsset) -> {
646 if (isDestroyed()) {
647 return;
648 }
649
650 homeWallpaper.getThumbAsset(appContext).loadDrawableWithTransition(
651 TopLevelPickerActivity.this,
652 mCurrentWallpaperImage,
653 200 /* transitionDurationMillis */,
654 () -> {
655 if (refreshListener != null) {
656 refreshListener.onCurrentWallpaperRefreshed();
657 }
658 },
659 Color.TRANSPARENT);
660 };
661 new FetchThumbAssetTask(appContext, homeWallpaper, assetReceiver).executeOnExecutor(
662 AsyncTask.THREAD_POOL_EXECUTOR);
663
664 mCurrentWallpaperPresentationMode.setText(
665 AttributionFormatter.getHumanReadableWallpaperPresentationMode(
666 TopLevelPickerActivity.this, presentationMode));
667
668 List<String> attributions = homeWallpaper.getAttributions(appContext);
669 if (attributions.size() > 0 && attributions.get(0) != null) {
670 mCurrentWallpaperTitle.setText(attributions.get(0));
671 }
672
673 mCurrentWallpaperSubtitle.setText(
674 AttributionFormatter.formatWallpaperSubtitle(appContext, homeWallpaper));
675
676 final String actionUrl = homeWallpaper.getActionUrl(appContext);
677 if (actionUrl != null && !actionUrl.isEmpty()) {
678 Uri exploreUri = Uri.parse(actionUrl);
679
680 ExploreIntentChecker intentChecker = injector.getExploreIntentChecker(appContext);
681 intentChecker.fetchValidActionViewIntent(exploreUri, (@Nullable Intent exploreIntent) -> {
682 if (exploreIntent != null && !isDestroyed()) {
Santiago Etchebehered1bd5092018-04-18 16:03:30 -0700683 // Set the icon for the button
684 Drawable exploreButtonDrawable = getResources().getDrawable(
Santiago Etchebeheree0810d02018-05-10 17:39:40 -0700685 homeWallpaper.getActionIconRes(appContext));
Santiago Etchebehered1bd5092018-04-18 16:03:30 -0700686
687 // This Drawable's state is shared across the app, so make a copy of it
688 // before applying a color tint as not to affect other clients elsewhere
689 // in the app.
690 exploreButtonDrawable = exploreButtonDrawable.getConstantState()
691 .newDrawable().mutate();
692 // Color the "compass" icon with the accent color.
693 exploreButtonDrawable.setColorFilter(
694 getResources().getColor(R.color.accent_color), Mode.SRC_IN);
695
696 ButtonDrawableSetterCompat.setDrawableToButtonStart(
697 mCurrentWallpaperExploreButton, exploreButtonDrawable);
698 mCurrentWallpaperExploreButton.setText(getString(
Santiago Etchebeheree0810d02018-05-10 17:39:40 -0700699 homeWallpaper.getActionLabelRes(appContext)));
Jon Miranda16ea1b12017-12-12 14:52:48 -0800700 mCurrentWallpaperExploreButton.setVisibility(View.VISIBLE);
701 mCurrentWallpaperExploreButton.setOnClickListener(new OnClickListener() {
702 @Override
703 public void onClick(View v) {
Santiago Etchebehered1bd5092018-04-18 16:03:30 -0700704 mUserEventLogger.logExploreClicked(
705 homeWallpaper.getCollectionId(appContext));
Jon Miranda16ea1b12017-12-12 14:52:48 -0800706 startActivity(exploreIntent);
707 }
708 });
709 }
710 });
711 } else {
712 mCurrentWallpaperExploreButton.setVisibility(View.GONE);
713 }
714
715 // Hide the wallpaper position options UI if the current home wallpaper is not from
716 // "my photos".
717 String homeCollectionId = homeWallpaper.getCollectionId(TopLevelPickerActivity.this);
718 if (mWallpaperPositionOptions != null
719 && homeCollectionId != null // May be null if app is being used for the first time.
720 && !homeCollectionId.equals(getString(R.string.image_wallpaper_collection_id))) {
721 mWallpaperPositionOptions.setVisibility(View.GONE);
722 }
723
724 boolean showSkipWallpaperButton = Flags.skipDailyWallpaperButtonEnabled
725 && presentationMode == WallpaperPreferences.PRESENTATION_MODE_ROTATING;
726 if (showSkipWallpaperButton) {
727 mCurrentWallpaperSkipWallpaperButton.setVisibility(View.VISIBLE);
728 mCurrentWallpaperSkipWallpaperButton.setOnClickListener(new OnClickListener() {
729 @Override
730 public void onClick(View v) {
731 refreshDailyWallpaper();
732 }
733 });
734 } else {
735 mCurrentWallpaperSkipWallpaperButton.setVisibility(View.GONE);
736 }
737
738 if (refreshListener != null) {
739 refreshListener.onCurrentWallpaperRefreshed();
740 }
741 }
742 }, true /* forceRefresh */);
743 }
744
745 @Override
746 public void onSaveInstanceState(Bundle savedInstanceState) {
747 FormFactorChecker formFactorChecker = InjectorProvider.getInjector().getFormFactorChecker(this);
748 if (formFactorChecker.getFormFactor() == FormFactorChecker.FORM_FACTOR_DESKTOP) {
749 TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout);
750
751 // tabLayout is only present when the main IndividualPickerFragment is present (as opposed to
752 // the WallpaperDisabledFragment), so need this null check.
753 if (tabLayout != null) {
754 savedInstanceState.putInt(KEY_SELECTED_CATEGORY_TAB, tabLayout.getSelectedTabPosition());
755 }
756 }
757
758 super.onSaveInstanceState(savedInstanceState);
759 }
760
761 /**
762 * Populates the categories appropriately depending on the device form factor.
763 *
764 * @param selectedTabPosition The position of the tab to show as selected, or -1 if no tab
765 * should be selected (i.e. because there is no tab layout present, as on MOBILE form factor).
766 * @param forceRefresh Whether to force a refresh of categories from the CategoryProvider. True if
767 * on first launch.
768 */
769 private void populateCategories(final int selectedTabPosition, boolean forceRefresh) {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800770
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700771 final CategoryPickerFragment categoryPickerFragment = getCategoryPickerFragment();
772
773 if (forceRefresh && categoryPickerFragment != null) {
774 categoryPickerFragment.clearCategories();
775 }
776
777 mCategoryProvider.fetchCategories(new CategoryReceiver() {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800778 @Override
779 public void onCategoryReceived(Category category) {
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700780 addCategory(category, true);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800781 }
782
783 @Override
784 public void doneFetchingCategories() {
785 if (mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE) {
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700786 notifyDoneFetchingCategories();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800787 } else { // DESKTOP
788 populateCategoryTabs(selectedTabPosition);
789 }
790 }
791 }, forceRefresh);
792 }
793
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700794 private void notifyDoneFetchingCategories() {
795 CategoryPickerFragment categoryPickerFragment = getCategoryPickerFragment();
796 if (categoryPickerFragment != null) {
797 categoryPickerFragment.doneFetchingCategories();
798 }
799 }
800
801 private void addCategory(Category category, boolean fetchingAll) {
802 CategoryPickerFragment categoryPickerFragment = getCategoryPickerFragment();
803 if (categoryPickerFragment != null) {
804 categoryPickerFragment.addCategory(category, fetchingAll);
805 }
806 }
807
808 private void removeCategory(Category category) {
809 CategoryPickerFragment categoryPickerFragment = getCategoryPickerFragment();
810 if (categoryPickerFragment != null) {
811 categoryPickerFragment.removeCategory(category);
812 }
813 }
814
815 private void updateCategory(Category category) {
816 CategoryPickerFragment categoryPickerFragment = getCategoryPickerFragment();
817 if (categoryPickerFragment != null) {
818 categoryPickerFragment.updateCategory(category);
819 }
820 }
821
822 @Nullable
823 private CategoryPickerFragment getCategoryPickerFragment() {
824 if (mFormFactor != FormFactorChecker.FORM_FACTOR_MOBILE) {
825 return null;
826 }
827 FragmentManager fm = getSupportFragmentManager();
828 return (CategoryPickerFragment) fm.findFragmentById(R.id.fragment_container);
829 }
830
Jon Miranda16ea1b12017-12-12 14:52:48 -0800831 /**
832 * Populates the category tabs on DESKTOP form factor.
833 *
834 * @param selectedTabPosition The position of the tab to show as selected, or -1 if no particular
835 * tab should be selected (in which case: the tab of the category for the currently set
836 * wallpaper will be selected if enumerable; if not, the first enumerable category's tab will
837 * be selected).
838 */
839 private void populateCategoryTabs(int selectedTabPosition) {
840 final TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout);
841 tabLayout.removeAllTabs();
842
843 String currentlySetCollectionId = mPreferences.getHomeWallpaperCollectionId();
844
845 Tab tabToSelect = null;
846 Tab firstEnumerableCategoryTab = null;
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700847 for (int i = 0; i < mCategoryProvider.getSize(); i++) {
848 Category category = mCategoryProvider.getCategory(i);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800849
850 Tab tab = tabLayout.newTab();
851 tab.setText(category.getTitle());
852 tab.setTag(category);
853 tabLayout.addTab(tab, false /* setSelected */);
854
855 if (firstEnumerableCategoryTab == null && category.isEnumerable()) {
856 firstEnumerableCategoryTab = tab;
857 }
858
859 boolean shouldSelectTab = (i == selectedTabPosition)
860 || (selectedTabPosition == -1
861 && tabToSelect == null
862 && category.isEnumerable()
863 && currentlySetCollectionId != null
864 && currentlySetCollectionId.equals(category.getCollectionId()));
865
866 if (shouldSelectTab) {
867 tabToSelect = tab;
868 }
869 }
870
871 // If the above loop did not identify a specific tab to select, then just select the tab for
872 // the first enumerable category.
873 if (tabToSelect == null) {
874 tabToSelect = firstEnumerableCategoryTab;
875 }
876
877 // There may be no enumerable tabs (e.g., offline case), so we need to null-check again.
878 if (tabToSelect != null) {
879 tabToSelect.select();
880 }
881 }
882
883 /**
884 * Refreshes the current wallpaper in a daily wallpaper rotation.
885 */
886 private void refreshDailyWallpaper() {
887 // ProgressDialog endlessly updates the UI thread, keeping it from going idle which therefore
888 // causes Espresso to hang once the dialog is shown.
889 if (!mTestingMode) {
890 int themeResId;
891 if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
892 themeResId = R.style.ProgressDialogThemePreL;
893 } else {
894 themeResId = R.style.LightDialogTheme;
895 }
896 mRefreshWallpaperProgressDialog = new ProgressDialog(this, themeResId);
897 mRefreshWallpaperProgressDialog.setTitle(null);
898 mRefreshWallpaperProgressDialog.setMessage(
899 getResources().getString(R.string.refreshing_daily_wallpaper_dialog_message));
900 mRefreshWallpaperProgressDialog.setIndeterminate(true);
901 mRefreshWallpaperProgressDialog.show();
902 }
903
904 WallpaperRotationRefresher wallpaperRotationRefresher =
905 InjectorProvider.getInjector().getWallpaperRotationRefresher();
906 wallpaperRotationRefresher.refreshWallpaper(this, new Listener() {
907 @Override
908 public void onRefreshed() {
909 if (isDestroyed()) {
910 return;
911 }
912
913 if (mRefreshWallpaperProgressDialog != null) {
914 mRefreshWallpaperProgressDialog.dismiss();
915 }
916
917 refreshCurrentWallpapers(null /* refreshListener */);
918 }
919
920 @Override
921 public void onError() {
922 if (mRefreshWallpaperProgressDialog != null) {
923 mRefreshWallpaperProgressDialog.dismiss();
924 }
925
926 AlertDialog errorDialog = new AlertDialog.Builder(
927 TopLevelPickerActivity.this, R.style.LightDialogTheme)
928 .setMessage(R.string.refresh_daily_wallpaper_failed_message)
929 .setPositiveButton(android.R.string.ok, null /* onClickListener */)
930 .create();
931 errorDialog.show();
932 }
933 });
934 }
935
936 @Override
937 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
938 super.onActivityResult(requestCode, resultCode, data);
939
940 if (requestCode == SHOW_CATEGORY_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
941 Uri imageUri = (data == null) ? null : data.getData();
942 if (imageUri != null) {
943 // User selected an image from the system picker, so launch the preview for that image.
944 ImageWallpaperInfo imageWallpaper = new ImageWallpaperInfo(imageUri);
945 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
946 setCustomPhotoWallpaper(imageWallpaper);
947 return;
948 }
949
950 imageWallpaper.showPreview(this, mPreviewIntentFactory, PREVIEW_WALLPAPER_REQUEST_CODE);
951 } else {
952 // User finished viewing a category without any data, which implies that the user previewed
953 // and selected a wallpaper in-app, so finish this activity.
954 finishActivityWithResultOk();
955 }
956 } else if (requestCode == PREVIEW_WALLPAPER_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
957 // User previewed and selected a wallpaper, so finish this activity.
958 finishActivityWithResultOk();
959 }
960 }
961
962 /**
963 * Shows the view-only preview activity for the given wallpaper.
964 */
965 public void showViewOnlyPreview(WallpaperInfo wallpaperInfo) {
966 wallpaperInfo.showPreview(
967 this, mViewOnlyPreviewIntentFactory, VIEW_ONLY_PREVIEW_WALLPAPER_REQUEST_CODE);
968 }
969
970 @Override
971 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
972 @NonNull int[] grantResults) {
973 if (requestCode == READ_EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE
974 && permissions.length > 0
975 && permissions[0].equals(permission.READ_EXTERNAL_STORAGE)
976 && grantResults.length > 0) {
977 if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
978 for (PermissionChangedListener listener : mPermissionChangedListeners) {
979 listener.onPermissionsGranted();
980 }
981 } else if (!shouldShowRequestPermissionRationale(permission.READ_EXTERNAL_STORAGE)) {
982 for (PermissionChangedListener listener : mPermissionChangedListeners) {
983 listener.onPermissionsDenied(true /* dontAskAgain */);
984 }
985 } else {
986 for (PermissionChangedListener listener : mPermissionChangedListeners) {
987 listener.onPermissionsDenied(false /* dontAskAgain */);
988 }
989 }
990 }
991 mPermissionChangedListeners.clear();
992 }
993
994 /**
995 * Shows the picker activity for the given category.
996 */
997 public void show(String collectionId) {
998 Category category = findCategoryForCollectionId(collectionId);
999 if (category == null) {
1000 return;
1001 }
1002
1003 if (mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE) {
1004 category.show(this, mPickerIntentFactory, SHOW_CATEGORY_REQUEST_CODE);
1005 } else { // DESKTOP
1006 showCategoryDesktop(collectionId);
1007 }
1008 }
1009
1010 private void reselectLastTab() {
1011 TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout);
1012
1013 // In the offline case, "My photos" could be the only category. Thus we need this check --
1014 // to ensure that we don't try to select the "previously selected" category which was -1.
1015 if (mLastSelectedCategoryTabIndex > -1) {
1016 Tab tabToSelect = tabLayout.getTabAt(mLastSelectedCategoryTabIndex);
1017 if (((Category) tabToSelect.getTag()).isEnumerable()) {
1018 tabToSelect.select();
1019 }
1020 }
1021 }
1022
1023 @Nullable
1024 private Category findCategoryForCollectionId(String collectionId) {
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -07001025 return mCategoryProvider.getCategory(collectionId);
Jon Miranda16ea1b12017-12-12 14:52:48 -08001026 }
1027
1028 private void showCategoryDesktop(String collectionId) {
1029 Category category = findCategoryForCollectionId(collectionId);
1030 if (category == null) {
1031 return;
1032 }
1033
1034 if (category.isEnumerable()) {
1035 // Replace contained IndividualPickerFragment with a new instance for the given category.
1036 final FragmentManager fm = getSupportFragmentManager();
1037 Fragment fragment = fm.findFragmentById(R.id.fragment_container);
1038 if (fragment != null) {
1039 fm.beginTransaction()
1040 .remove(fragment)
1041 .commit();
1042 }
1043 IndividualPickerFragment newFragment = IndividualPickerFragment.newInstance(collectionId);
1044 fm.beginTransaction()
1045 .add(R.id.fragment_container, newFragment)
1046 .commit();
1047 newFragment.setCurrentWallpaperBottomSheetPresenter(this);
1048 newFragment.setWallpapersUiContainer(this);
1049 } else {
1050 category.show(this, mPickerIntentFactory, SHOW_CATEGORY_REQUEST_CODE);
1051
1052 // Need to select the tab here in case we are coming back from a "My photos" in which case
1053 // the tab would have been set to "My photos" while viewing a regular image category.
1054 reselectLastTab();
1055 }
1056 }
1057
1058 private void finishActivityWithResultOk() {
1059 overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
1060 setResult(Activity.RESULT_OK);
1061 finish();
1062 }
1063
1064 @WallpaperSupportLevel
1065 private int getWallpaperSupportLevel() {
1066 WallpaperManager wallpaperManager = WallpaperManager.getInstance(this);
1067
1068 if (VERSION.SDK_INT >= VERSION_CODES.N) {
1069 if (wallpaperManager.isWallpaperSupported()) {
1070 return wallpaperManager.isSetWallpaperAllowed()
1071 ? WallpaperDisabledFragment.SUPPORTED_CAN_SET
1072 : WallpaperDisabledFragment.NOT_SUPPORTED_BLOCKED_BY_ADMIN;
1073 }
1074 return WallpaperDisabledFragment.NOT_SUPPORTED_BY_DEVICE;
1075 } else if (VERSION.SDK_INT >= VERSION_CODES.M) {
1076 return wallpaperManager.isWallpaperSupported() ? WallpaperDisabledFragment.SUPPORTED_CAN_SET
1077 : WallpaperDisabledFragment.NOT_SUPPORTED_BY_DEVICE;
1078 } else {
1079 WallpaperManagerCompat wallpaperManagerCompat =
1080 InjectorProvider.getInjector().getWallpaperManagerCompat(this);
1081 boolean isSupported = wallpaperManagerCompat.getDrawable() != null;
1082 wallpaperManager.forgetLoadedWallpaper();
1083 return isSupported ? WallpaperDisabledFragment.SUPPORTED_CAN_SET
1084 : WallpaperDisabledFragment.NOT_SUPPORTED_BY_DEVICE;
1085 }
1086 }
1087
1088 @Override
1089 public void setCurrentWallpapersExpanded(boolean expanded) {
1090 final BottomSheetBehavior<LinearLayout> bottomSheetBehavior =
1091 BottomSheetBehavior.from(mBottomSheet);
1092 bottomSheetBehavior.setState(
1093 expanded ? BottomSheetBehavior.STATE_EXPANDED : BottomSheetBehavior.STATE_COLLAPSED);
1094 }
1095
1096 @Override
1097 public void onWallpapersReady() {
1098 setDesktopLoading(false);
1099 setCurrentWallpapersExpanded(true);
1100 }
1101
1102 @Override
1103 public void onClickTryAgain(@Destination int unused) {
1104 // Retry the set wallpaper operation with the default center-crop setting.
1105 if (mPendingSetWallpaperInfo != null) {
1106 setCustomPhotoWallpaper(mPendingSetWallpaperInfo);
1107 }
1108 }
1109
1110 /**
1111 * Sets the provides wallpaper to the device with center-cropped and scaled to fit the device's
1112 * default display.
1113 */
1114 private void setCustomPhotoWallpaper(final WallpaperInfo wallpaper) {
1115 // Save this WallpaperInfo so we can retry this operation later if it fails.
1116 mPendingSetWallpaperInfo = wallpaper;
1117
1118 showSettingWallpaperProgressDialog();
1119
1120 mWallpaperPersister.setIndividualWallpaperWithPosition(this, wallpaper,
1121 WallpaperPersister.WALLPAPER_POSITION_CENTER_CROP, new SetWallpaperCallback() {
1122 @Override
1123 public void onSuccess() {
1124 dismissSettingWallpaperProgressDialog();
1125 refreshCurrentWallpapers(null /* refreshListener */);
1126
1127 mPreferences.setPendingWallpaperSetStatus(
1128 WallpaperPreferences.WALLPAPER_SET_NOT_PENDING);
1129 mUserEventLogger.logWallpaperSet(
1130 wallpaper.getCollectionId(getApplicationContext()),
1131 wallpaper.getWallpaperId());
1132 mUserEventLogger.logWallpaperSetResult(UserEventLogger.WALLPAPER_SET_RESULT_SUCCESS);
1133
1134 // The user may have closed the activity before the set wallpaper operation completed.
1135 if (isDestroyed()) {
1136 return;
1137 }
1138
1139 // Show the wallpaper crop option selector and bind click event handlers.
1140 mWallpaperPositionOptions.setVisibility(View.VISIBLE);
1141
1142 mWasCustomPhotoWallpaperSet = true;
1143 mCustomPhotoWallpaperPosition = WallpaperPersister.WALLPAPER_POSITION_CENTER_CROP;
1144
1145 initializeWallpaperPositionOptionClickHandlers(wallpaper);
1146 }
1147
1148 @Override
1149 public void onError(Throwable throwable) {
1150 dismissSettingWallpaperProgressDialog();
1151 showSetWallpaperErrorDialog();
1152
1153 mPreferences.setPendingWallpaperSetStatus(
1154 WallpaperPreferences.WALLPAPER_SET_NOT_PENDING);
1155 mUserEventLogger.logWallpaperSetResult(
1156 UserEventLogger.WALLPAPER_SET_RESULT_FAILURE);
1157 @WallpaperSetFailureReason int failureReason = ThrowableAnalyzer.isOOM(throwable)
1158 ? UserEventLogger.WALLPAPER_SET_FAILURE_REASON_OOM
1159 : UserEventLogger.WALLPAPER_SET_FAILURE_REASON_OTHER;
1160 mUserEventLogger.logWallpaperSetFailureReason(failureReason);
1161 Log.e(TAG, "Unable to set wallpaper from 'my photos'.");
1162 }
1163 });
1164 }
1165
1166 /**
1167 * Initializes the wallpaper position button click handlers to change the way the provided
1168 * wallpaper is set to the device.
1169 */
1170 private void initializeWallpaperPositionOptionClickHandlers(final WallpaperInfo wallpaperInfo) {
1171 Button centerCropOptionBtn = (Button) findViewById(R.id.wallpaper_position_option_center_crop);
1172 Button stretchOptionBtn = (Button) findViewById(R.id.wallpaper_position_option_stretched);
1173 Button centerOptionBtn = (Button) findViewById(R.id.wallpaper_position_option_center);
1174
1175 // The "center crop" wallpaper position button is selected by default.
1176 setCenterCropWallpaperPositionButtonSelected(centerCropOptionBtn, true /* isSelected */);
1177 centerCropOptionBtn.setOnClickListener(new OnClickListener() {
1178 @Override
1179 public void onClick(View view) {
1180 mWallpaperPersister.setIndividualWallpaperWithPosition(TopLevelPickerActivity.this,
1181 wallpaperInfo, WallpaperPersister.WALLPAPER_POSITION_CENTER_CROP,
1182 new SetWallpaperCallback() {
1183 @Override
1184 public void onSuccess() {
1185 // The user may have closed the activity before the set wallpaper operation
1186 // completed.
1187 if (isDestroyed()) {
1188 return;
1189 }
1190
1191 refreshCurrentWallpapers(null /* refreshListener */);
1192
1193 setCenterCropWallpaperPositionButtonSelected(
1194 centerCropOptionBtn, true /* isSelected */);
1195 setCenterWallpaperPositionButtonSelected(centerOptionBtn, false /* isSelected */);
1196 setStretchWallpaperPositionButtonSelected(stretchOptionBtn, false /* isSelected */);
1197
1198 mCustomPhotoWallpaperPosition = WallpaperPersister.WALLPAPER_POSITION_CENTER_CROP;
1199 }
1200
1201 @Override
1202 public void onError(@Nullable Throwable throwable) {
1203 // no-op
1204 }
1205 });
1206 }
1207 });
1208
1209 // "Stretch" is not selected by default.
1210 setStretchWallpaperPositionButtonSelected(stretchOptionBtn, false /* isSelected */);
1211 stretchOptionBtn.setOnClickListener(new OnClickListener() {
1212 @Override
1213 public void onClick(View view) {
1214 mWallpaperPersister.setIndividualWallpaperWithPosition(TopLevelPickerActivity.this,
1215 wallpaperInfo, WallpaperPersister.WALLPAPER_POSITION_STRETCH,
1216 new SetWallpaperCallback() {
1217 @Override
1218 public void onSuccess() {
1219 // The user may have closed the activity before the set wallpaper operation
1220 // completed.
1221 if (isDestroyed()) {
1222 return;
1223 }
1224
1225 refreshCurrentWallpapers(null /* refreshListener */);
1226
1227 setStretchWallpaperPositionButtonSelected(stretchOptionBtn, true /* isSelected */);
1228 setCenterCropWallpaperPositionButtonSelected(
1229 centerCropOptionBtn, false /* isSelected */);
1230 setCenterWallpaperPositionButtonSelected(centerOptionBtn, false /* isSelected */);
1231
1232 mCustomPhotoWallpaperPosition = WallpaperPersister.WALLPAPER_POSITION_STRETCH;
1233 }
1234
1235 @Override
1236 public void onError(@Nullable Throwable throwable) {
1237 // no-op
1238 }
1239 });
1240 }
1241 });
1242
1243 // "Center" is not selected by default.
1244 setCenterWallpaperPositionButtonSelected(centerOptionBtn, false /* isSelected */);
1245 centerOptionBtn.setOnClickListener(new OnClickListener() {
1246 @Override
1247 public void onClick(View view) {
1248 mWallpaperPersister.setIndividualWallpaperWithPosition(TopLevelPickerActivity.this,
1249 wallpaperInfo, WallpaperPersister.WALLPAPER_POSITION_CENTER,
1250 new SetWallpaperCallback() {
1251 @Override
1252 public void onSuccess() {
1253 // The user may have closed the activity before the set wallpaper operation
1254 // completed.
1255 if (isDestroyed()) {
1256 return;
1257 }
1258
1259 refreshCurrentWallpapers(null /* refreshListener */);
1260
1261 setCenterWallpaperPositionButtonSelected(centerOptionBtn, true /* isSelected */);
1262 setCenterCropWallpaperPositionButtonSelected(
1263 centerCropOptionBtn, false /* isSelected */);
1264 setStretchWallpaperPositionButtonSelected(stretchOptionBtn, false /* isSelected */);
1265
1266 mCustomPhotoWallpaperPosition = WallpaperPersister.WALLPAPER_POSITION_CENTER;
1267 }
1268
1269 @Override
1270 public void onError(@Nullable Throwable throwable) {
1271 // no-op
1272 }
1273 });
1274 }
1275 });
1276 }
1277
1278 private void setCenterWallpaperPositionButtonSelected(Button button, boolean isSelected) {
1279 int drawableId = isSelected ? R.drawable.center_blue : R.drawable.center_grey;
1280 ButtonDrawableSetterCompat.setDrawableToButtonStart(button, getDrawable(drawableId));
1281 button.setTextColor(getColor(getTextColorIdForWallpaperPositionButton(isSelected)));
1282 }
1283
1284 private void setCenterCropWallpaperPositionButtonSelected(Button button, boolean isSelected) {
1285 int drawableId = isSelected ? R.drawable.center_crop_blue : R.drawable.center_crop_grey;
1286 ButtonDrawableSetterCompat.setDrawableToButtonStart(button, getDrawable(drawableId));
1287 button.setTextColor(getColor(getTextColorIdForWallpaperPositionButton(isSelected)));
1288 }
1289
1290 private void setStretchWallpaperPositionButtonSelected(Button button, boolean isSelected) {
1291 int drawableId = isSelected ? R.drawable.stretch_blue : R.drawable.stretch_grey;
1292 ButtonDrawableSetterCompat.setDrawableToButtonStart(button, getDrawable(drawableId));
1293 button.setTextColor(getColor(getTextColorIdForWallpaperPositionButton(isSelected)));
1294 }
1295
1296 private void showSettingWallpaperProgressDialog() {
1297 // ProgressDialog endlessly updates the UI thread, keeping it from going idle which therefore
1298 // causes Espresso to hang once the dialog is shown.
1299 if (!mTestingMode) {
1300 int themeResId;
1301 if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
1302 themeResId = R.style.ProgressDialogThemePreL;
1303 } else {
1304 themeResId = R.style.LightDialogTheme;
1305 }
1306 mSetWallpaperProgressDialog = new ProgressDialog(this, themeResId);
1307 mSetWallpaperProgressDialog.setTitle(null);
1308 mSetWallpaperProgressDialog.setMessage(
1309 getResources().getString(R.string.set_wallpaper_progress_message));
1310 mSetWallpaperProgressDialog.setIndeterminate(true);
1311 mSetWallpaperProgressDialog.show();
1312 }
1313 }
1314
1315 private void dismissSettingWallpaperProgressDialog() {
1316 if (mSetWallpaperProgressDialog != null) {
1317 mSetWallpaperProgressDialog.dismiss();
1318 }
1319 }
1320
1321 private void showSetWallpaperErrorDialog() {
1322 SetWallpaperErrorDialogFragment dialogFragment = SetWallpaperErrorDialogFragment.newInstance(
1323 R.string.set_wallpaper_error_message, WallpaperPersister.DEST_BOTH);
1324
1325 if (isSafeToCommitFragmentTransaction()) {
1326 dialogFragment.show(getSupportFragmentManager(), TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT);
1327 } else {
1328 mStagedSetWallpaperErrorDialogFragment = dialogFragment;
1329 }
1330 }
1331
1332 private interface AssetReceiver {
1333 void onAssetReceived(Asset asset);
1334 }
1335
1336 /**
1337 * An AsyncTask for asynchronously fetching the thumbnail asset for a given WallpaperInfo.
1338 * Used to work around expensive method call to WallpaperManager#getWallpaperFile made from the
1339 * CurrentWallpaperInfoVN getAsset() method.
1340 */
1341 private static class FetchThumbAssetTask extends AsyncTask<Void, Void, Asset> {
1342 private Context mAppContext;
1343 private WallpaperInfo mWallpaperInfo;
1344 private AssetReceiver mReceiver;
1345
1346 public FetchThumbAssetTask(Context appContext, WallpaperInfo wallpaperInfo,
1347 AssetReceiver receiver) {
1348 mAppContext = appContext;
1349 mWallpaperInfo = wallpaperInfo;
1350 mReceiver = receiver;
1351 }
1352
1353 @Override
1354 protected Asset doInBackground(Void... params) {
1355 return mWallpaperInfo.getThumbAsset(mAppContext);
1356 }
1357
1358 @Override
1359 protected void onPostExecute(Asset thumbAsset) {
1360 mReceiver.onAssetReceived(thumbAsset);
1361 }
1362 }
1363}