blob: 898a388810cc11445945f6616786aa4e1cc9fb9e [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 Etchebeherece5613f2018-06-01 13:22:47 -0700704 mUserEventLogger.logActionClicked(
705 homeWallpaper.getCollectionId(appContext),
706 homeWallpaper.getActionLabelRes(appContext));
Jon Miranda16ea1b12017-12-12 14:52:48 -0800707 startActivity(exploreIntent);
708 }
709 });
710 }
711 });
712 } else {
713 mCurrentWallpaperExploreButton.setVisibility(View.GONE);
714 }
715
716 // Hide the wallpaper position options UI if the current home wallpaper is not from
717 // "my photos".
718 String homeCollectionId = homeWallpaper.getCollectionId(TopLevelPickerActivity.this);
719 if (mWallpaperPositionOptions != null
720 && homeCollectionId != null // May be null if app is being used for the first time.
721 && !homeCollectionId.equals(getString(R.string.image_wallpaper_collection_id))) {
722 mWallpaperPositionOptions.setVisibility(View.GONE);
723 }
724
725 boolean showSkipWallpaperButton = Flags.skipDailyWallpaperButtonEnabled
726 && presentationMode == WallpaperPreferences.PRESENTATION_MODE_ROTATING;
727 if (showSkipWallpaperButton) {
728 mCurrentWallpaperSkipWallpaperButton.setVisibility(View.VISIBLE);
729 mCurrentWallpaperSkipWallpaperButton.setOnClickListener(new OnClickListener() {
730 @Override
731 public void onClick(View v) {
732 refreshDailyWallpaper();
733 }
734 });
735 } else {
736 mCurrentWallpaperSkipWallpaperButton.setVisibility(View.GONE);
737 }
738
739 if (refreshListener != null) {
740 refreshListener.onCurrentWallpaperRefreshed();
741 }
742 }
743 }, true /* forceRefresh */);
744 }
745
746 @Override
747 public void onSaveInstanceState(Bundle savedInstanceState) {
748 FormFactorChecker formFactorChecker = InjectorProvider.getInjector().getFormFactorChecker(this);
749 if (formFactorChecker.getFormFactor() == FormFactorChecker.FORM_FACTOR_DESKTOP) {
750 TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout);
751
752 // tabLayout is only present when the main IndividualPickerFragment is present (as opposed to
753 // the WallpaperDisabledFragment), so need this null check.
754 if (tabLayout != null) {
755 savedInstanceState.putInt(KEY_SELECTED_CATEGORY_TAB, tabLayout.getSelectedTabPosition());
756 }
757 }
758
759 super.onSaveInstanceState(savedInstanceState);
760 }
761
762 /**
763 * Populates the categories appropriately depending on the device form factor.
764 *
765 * @param selectedTabPosition The position of the tab to show as selected, or -1 if no tab
766 * should be selected (i.e. because there is no tab layout present, as on MOBILE form factor).
767 * @param forceRefresh Whether to force a refresh of categories from the CategoryProvider. True if
768 * on first launch.
769 */
770 private void populateCategories(final int selectedTabPosition, boolean forceRefresh) {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800771
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700772 final CategoryPickerFragment categoryPickerFragment = getCategoryPickerFragment();
773
774 if (forceRefresh && categoryPickerFragment != null) {
775 categoryPickerFragment.clearCategories();
776 }
777
778 mCategoryProvider.fetchCategories(new CategoryReceiver() {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800779 @Override
780 public void onCategoryReceived(Category category) {
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700781 addCategory(category, true);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800782 }
783
784 @Override
785 public void doneFetchingCategories() {
786 if (mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE) {
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700787 notifyDoneFetchingCategories();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800788 } else { // DESKTOP
789 populateCategoryTabs(selectedTabPosition);
790 }
791 }
792 }, forceRefresh);
793 }
794
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700795 private void notifyDoneFetchingCategories() {
796 CategoryPickerFragment categoryPickerFragment = getCategoryPickerFragment();
797 if (categoryPickerFragment != null) {
798 categoryPickerFragment.doneFetchingCategories();
799 }
800 }
801
802 private void addCategory(Category category, boolean fetchingAll) {
803 CategoryPickerFragment categoryPickerFragment = getCategoryPickerFragment();
804 if (categoryPickerFragment != null) {
805 categoryPickerFragment.addCategory(category, fetchingAll);
806 }
807 }
808
809 private void removeCategory(Category category) {
810 CategoryPickerFragment categoryPickerFragment = getCategoryPickerFragment();
811 if (categoryPickerFragment != null) {
812 categoryPickerFragment.removeCategory(category);
813 }
814 }
815
816 private void updateCategory(Category category) {
817 CategoryPickerFragment categoryPickerFragment = getCategoryPickerFragment();
818 if (categoryPickerFragment != null) {
819 categoryPickerFragment.updateCategory(category);
820 }
821 }
822
823 @Nullable
824 private CategoryPickerFragment getCategoryPickerFragment() {
825 if (mFormFactor != FormFactorChecker.FORM_FACTOR_MOBILE) {
826 return null;
827 }
828 FragmentManager fm = getSupportFragmentManager();
829 return (CategoryPickerFragment) fm.findFragmentById(R.id.fragment_container);
830 }
831
Jon Miranda16ea1b12017-12-12 14:52:48 -0800832 /**
833 * Populates the category tabs on DESKTOP form factor.
834 *
835 * @param selectedTabPosition The position of the tab to show as selected, or -1 if no particular
836 * tab should be selected (in which case: the tab of the category for the currently set
837 * wallpaper will be selected if enumerable; if not, the first enumerable category's tab will
838 * be selected).
839 */
840 private void populateCategoryTabs(int selectedTabPosition) {
841 final TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout);
842 tabLayout.removeAllTabs();
843
844 String currentlySetCollectionId = mPreferences.getHomeWallpaperCollectionId();
845
846 Tab tabToSelect = null;
847 Tab firstEnumerableCategoryTab = null;
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700848 for (int i = 0; i < mCategoryProvider.getSize(); i++) {
849 Category category = mCategoryProvider.getCategory(i);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800850
851 Tab tab = tabLayout.newTab();
852 tab.setText(category.getTitle());
853 tab.setTag(category);
854 tabLayout.addTab(tab, false /* setSelected */);
855
856 if (firstEnumerableCategoryTab == null && category.isEnumerable()) {
857 firstEnumerableCategoryTab = tab;
858 }
859
860 boolean shouldSelectTab = (i == selectedTabPosition)
861 || (selectedTabPosition == -1
862 && tabToSelect == null
863 && category.isEnumerable()
864 && currentlySetCollectionId != null
865 && currentlySetCollectionId.equals(category.getCollectionId()));
866
867 if (shouldSelectTab) {
868 tabToSelect = tab;
869 }
870 }
871
872 // If the above loop did not identify a specific tab to select, then just select the tab for
873 // the first enumerable category.
874 if (tabToSelect == null) {
875 tabToSelect = firstEnumerableCategoryTab;
876 }
877
878 // There may be no enumerable tabs (e.g., offline case), so we need to null-check again.
879 if (tabToSelect != null) {
880 tabToSelect.select();
881 }
882 }
883
884 /**
885 * Refreshes the current wallpaper in a daily wallpaper rotation.
886 */
887 private void refreshDailyWallpaper() {
888 // ProgressDialog endlessly updates the UI thread, keeping it from going idle which therefore
889 // causes Espresso to hang once the dialog is shown.
890 if (!mTestingMode) {
891 int themeResId;
892 if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
893 themeResId = R.style.ProgressDialogThemePreL;
894 } else {
895 themeResId = R.style.LightDialogTheme;
896 }
897 mRefreshWallpaperProgressDialog = new ProgressDialog(this, themeResId);
898 mRefreshWallpaperProgressDialog.setTitle(null);
899 mRefreshWallpaperProgressDialog.setMessage(
900 getResources().getString(R.string.refreshing_daily_wallpaper_dialog_message));
901 mRefreshWallpaperProgressDialog.setIndeterminate(true);
902 mRefreshWallpaperProgressDialog.show();
903 }
904
905 WallpaperRotationRefresher wallpaperRotationRefresher =
906 InjectorProvider.getInjector().getWallpaperRotationRefresher();
907 wallpaperRotationRefresher.refreshWallpaper(this, new Listener() {
908 @Override
909 public void onRefreshed() {
910 if (isDestroyed()) {
911 return;
912 }
913
914 if (mRefreshWallpaperProgressDialog != null) {
915 mRefreshWallpaperProgressDialog.dismiss();
916 }
917
918 refreshCurrentWallpapers(null /* refreshListener */);
919 }
920
921 @Override
922 public void onError() {
923 if (mRefreshWallpaperProgressDialog != null) {
924 mRefreshWallpaperProgressDialog.dismiss();
925 }
926
927 AlertDialog errorDialog = new AlertDialog.Builder(
928 TopLevelPickerActivity.this, R.style.LightDialogTheme)
929 .setMessage(R.string.refresh_daily_wallpaper_failed_message)
930 .setPositiveButton(android.R.string.ok, null /* onClickListener */)
931 .create();
932 errorDialog.show();
933 }
934 });
935 }
936
937 @Override
938 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
939 super.onActivityResult(requestCode, resultCode, data);
940
941 if (requestCode == SHOW_CATEGORY_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
942 Uri imageUri = (data == null) ? null : data.getData();
943 if (imageUri != null) {
944 // User selected an image from the system picker, so launch the preview for that image.
945 ImageWallpaperInfo imageWallpaper = new ImageWallpaperInfo(imageUri);
946 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
947 setCustomPhotoWallpaper(imageWallpaper);
948 return;
949 }
950
951 imageWallpaper.showPreview(this, mPreviewIntentFactory, PREVIEW_WALLPAPER_REQUEST_CODE);
952 } else {
953 // User finished viewing a category without any data, which implies that the user previewed
954 // and selected a wallpaper in-app, so finish this activity.
955 finishActivityWithResultOk();
956 }
957 } else if (requestCode == PREVIEW_WALLPAPER_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
958 // User previewed and selected a wallpaper, so finish this activity.
959 finishActivityWithResultOk();
960 }
961 }
962
963 /**
964 * Shows the view-only preview activity for the given wallpaper.
965 */
966 public void showViewOnlyPreview(WallpaperInfo wallpaperInfo) {
967 wallpaperInfo.showPreview(
968 this, mViewOnlyPreviewIntentFactory, VIEW_ONLY_PREVIEW_WALLPAPER_REQUEST_CODE);
969 }
970
971 @Override
972 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
973 @NonNull int[] grantResults) {
974 if (requestCode == READ_EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE
975 && permissions.length > 0
976 && permissions[0].equals(permission.READ_EXTERNAL_STORAGE)
977 && grantResults.length > 0) {
978 if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
979 for (PermissionChangedListener listener : mPermissionChangedListeners) {
980 listener.onPermissionsGranted();
981 }
982 } else if (!shouldShowRequestPermissionRationale(permission.READ_EXTERNAL_STORAGE)) {
983 for (PermissionChangedListener listener : mPermissionChangedListeners) {
984 listener.onPermissionsDenied(true /* dontAskAgain */);
985 }
986 } else {
987 for (PermissionChangedListener listener : mPermissionChangedListeners) {
988 listener.onPermissionsDenied(false /* dontAskAgain */);
989 }
990 }
991 }
992 mPermissionChangedListeners.clear();
993 }
994
995 /**
996 * Shows the picker activity for the given category.
997 */
998 public void show(String collectionId) {
999 Category category = findCategoryForCollectionId(collectionId);
1000 if (category == null) {
1001 return;
1002 }
1003
1004 if (mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE) {
1005 category.show(this, mPickerIntentFactory, SHOW_CATEGORY_REQUEST_CODE);
1006 } else { // DESKTOP
1007 showCategoryDesktop(collectionId);
1008 }
1009 }
1010
1011 private void reselectLastTab() {
1012 TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout);
1013
1014 // In the offline case, "My photos" could be the only category. Thus we need this check --
1015 // to ensure that we don't try to select the "previously selected" category which was -1.
1016 if (mLastSelectedCategoryTabIndex > -1) {
1017 Tab tabToSelect = tabLayout.getTabAt(mLastSelectedCategoryTabIndex);
1018 if (((Category) tabToSelect.getTag()).isEnumerable()) {
1019 tabToSelect.select();
1020 }
1021 }
1022 }
1023
1024 @Nullable
1025 private Category findCategoryForCollectionId(String collectionId) {
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -07001026 return mCategoryProvider.getCategory(collectionId);
Jon Miranda16ea1b12017-12-12 14:52:48 -08001027 }
1028
1029 private void showCategoryDesktop(String collectionId) {
1030 Category category = findCategoryForCollectionId(collectionId);
1031 if (category == null) {
1032 return;
1033 }
1034
1035 if (category.isEnumerable()) {
1036 // Replace contained IndividualPickerFragment with a new instance for the given category.
1037 final FragmentManager fm = getSupportFragmentManager();
1038 Fragment fragment = fm.findFragmentById(R.id.fragment_container);
1039 if (fragment != null) {
1040 fm.beginTransaction()
1041 .remove(fragment)
1042 .commit();
1043 }
1044 IndividualPickerFragment newFragment = IndividualPickerFragment.newInstance(collectionId);
1045 fm.beginTransaction()
1046 .add(R.id.fragment_container, newFragment)
1047 .commit();
1048 newFragment.setCurrentWallpaperBottomSheetPresenter(this);
1049 newFragment.setWallpapersUiContainer(this);
1050 } else {
1051 category.show(this, mPickerIntentFactory, SHOW_CATEGORY_REQUEST_CODE);
1052
1053 // Need to select the tab here in case we are coming back from a "My photos" in which case
1054 // the tab would have been set to "My photos" while viewing a regular image category.
1055 reselectLastTab();
1056 }
1057 }
1058
1059 private void finishActivityWithResultOk() {
1060 overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
1061 setResult(Activity.RESULT_OK);
1062 finish();
1063 }
1064
1065 @WallpaperSupportLevel
1066 private int getWallpaperSupportLevel() {
1067 WallpaperManager wallpaperManager = WallpaperManager.getInstance(this);
1068
1069 if (VERSION.SDK_INT >= VERSION_CODES.N) {
1070 if (wallpaperManager.isWallpaperSupported()) {
1071 return wallpaperManager.isSetWallpaperAllowed()
1072 ? WallpaperDisabledFragment.SUPPORTED_CAN_SET
1073 : WallpaperDisabledFragment.NOT_SUPPORTED_BLOCKED_BY_ADMIN;
1074 }
1075 return WallpaperDisabledFragment.NOT_SUPPORTED_BY_DEVICE;
1076 } else if (VERSION.SDK_INT >= VERSION_CODES.M) {
1077 return wallpaperManager.isWallpaperSupported() ? WallpaperDisabledFragment.SUPPORTED_CAN_SET
1078 : WallpaperDisabledFragment.NOT_SUPPORTED_BY_DEVICE;
1079 } else {
1080 WallpaperManagerCompat wallpaperManagerCompat =
1081 InjectorProvider.getInjector().getWallpaperManagerCompat(this);
1082 boolean isSupported = wallpaperManagerCompat.getDrawable() != null;
1083 wallpaperManager.forgetLoadedWallpaper();
1084 return isSupported ? WallpaperDisabledFragment.SUPPORTED_CAN_SET
1085 : WallpaperDisabledFragment.NOT_SUPPORTED_BY_DEVICE;
1086 }
1087 }
1088
1089 @Override
1090 public void setCurrentWallpapersExpanded(boolean expanded) {
1091 final BottomSheetBehavior<LinearLayout> bottomSheetBehavior =
1092 BottomSheetBehavior.from(mBottomSheet);
1093 bottomSheetBehavior.setState(
1094 expanded ? BottomSheetBehavior.STATE_EXPANDED : BottomSheetBehavior.STATE_COLLAPSED);
1095 }
1096
1097 @Override
1098 public void onWallpapersReady() {
1099 setDesktopLoading(false);
1100 setCurrentWallpapersExpanded(true);
1101 }
1102
1103 @Override
1104 public void onClickTryAgain(@Destination int unused) {
1105 // Retry the set wallpaper operation with the default center-crop setting.
1106 if (mPendingSetWallpaperInfo != null) {
1107 setCustomPhotoWallpaper(mPendingSetWallpaperInfo);
1108 }
1109 }
1110
1111 /**
1112 * Sets the provides wallpaper to the device with center-cropped and scaled to fit the device's
1113 * default display.
1114 */
1115 private void setCustomPhotoWallpaper(final WallpaperInfo wallpaper) {
1116 // Save this WallpaperInfo so we can retry this operation later if it fails.
1117 mPendingSetWallpaperInfo = wallpaper;
1118
1119 showSettingWallpaperProgressDialog();
1120
1121 mWallpaperPersister.setIndividualWallpaperWithPosition(this, wallpaper,
1122 WallpaperPersister.WALLPAPER_POSITION_CENTER_CROP, new SetWallpaperCallback() {
1123 @Override
1124 public void onSuccess() {
1125 dismissSettingWallpaperProgressDialog();
1126 refreshCurrentWallpapers(null /* refreshListener */);
1127
1128 mPreferences.setPendingWallpaperSetStatus(
1129 WallpaperPreferences.WALLPAPER_SET_NOT_PENDING);
1130 mUserEventLogger.logWallpaperSet(
1131 wallpaper.getCollectionId(getApplicationContext()),
1132 wallpaper.getWallpaperId());
1133 mUserEventLogger.logWallpaperSetResult(UserEventLogger.WALLPAPER_SET_RESULT_SUCCESS);
1134
1135 // The user may have closed the activity before the set wallpaper operation completed.
1136 if (isDestroyed()) {
1137 return;
1138 }
1139
1140 // Show the wallpaper crop option selector and bind click event handlers.
1141 mWallpaperPositionOptions.setVisibility(View.VISIBLE);
1142
1143 mWasCustomPhotoWallpaperSet = true;
1144 mCustomPhotoWallpaperPosition = WallpaperPersister.WALLPAPER_POSITION_CENTER_CROP;
1145
1146 initializeWallpaperPositionOptionClickHandlers(wallpaper);
1147 }
1148
1149 @Override
1150 public void onError(Throwable throwable) {
1151 dismissSettingWallpaperProgressDialog();
1152 showSetWallpaperErrorDialog();
1153
1154 mPreferences.setPendingWallpaperSetStatus(
1155 WallpaperPreferences.WALLPAPER_SET_NOT_PENDING);
1156 mUserEventLogger.logWallpaperSetResult(
1157 UserEventLogger.WALLPAPER_SET_RESULT_FAILURE);
1158 @WallpaperSetFailureReason int failureReason = ThrowableAnalyzer.isOOM(throwable)
1159 ? UserEventLogger.WALLPAPER_SET_FAILURE_REASON_OOM
1160 : UserEventLogger.WALLPAPER_SET_FAILURE_REASON_OTHER;
1161 mUserEventLogger.logWallpaperSetFailureReason(failureReason);
1162 Log.e(TAG, "Unable to set wallpaper from 'my photos'.");
1163 }
1164 });
1165 }
1166
1167 /**
1168 * Initializes the wallpaper position button click handlers to change the way the provided
1169 * wallpaper is set to the device.
1170 */
1171 private void initializeWallpaperPositionOptionClickHandlers(final WallpaperInfo wallpaperInfo) {
1172 Button centerCropOptionBtn = (Button) findViewById(R.id.wallpaper_position_option_center_crop);
1173 Button stretchOptionBtn = (Button) findViewById(R.id.wallpaper_position_option_stretched);
1174 Button centerOptionBtn = (Button) findViewById(R.id.wallpaper_position_option_center);
1175
1176 // The "center crop" wallpaper position button is selected by default.
1177 setCenterCropWallpaperPositionButtonSelected(centerCropOptionBtn, true /* isSelected */);
1178 centerCropOptionBtn.setOnClickListener(new OnClickListener() {
1179 @Override
1180 public void onClick(View view) {
1181 mWallpaperPersister.setIndividualWallpaperWithPosition(TopLevelPickerActivity.this,
1182 wallpaperInfo, WallpaperPersister.WALLPAPER_POSITION_CENTER_CROP,
1183 new SetWallpaperCallback() {
1184 @Override
1185 public void onSuccess() {
1186 // The user may have closed the activity before the set wallpaper operation
1187 // completed.
1188 if (isDestroyed()) {
1189 return;
1190 }
1191
1192 refreshCurrentWallpapers(null /* refreshListener */);
1193
1194 setCenterCropWallpaperPositionButtonSelected(
1195 centerCropOptionBtn, true /* isSelected */);
1196 setCenterWallpaperPositionButtonSelected(centerOptionBtn, false /* isSelected */);
1197 setStretchWallpaperPositionButtonSelected(stretchOptionBtn, false /* isSelected */);
1198
1199 mCustomPhotoWallpaperPosition = WallpaperPersister.WALLPAPER_POSITION_CENTER_CROP;
1200 }
1201
1202 @Override
1203 public void onError(@Nullable Throwable throwable) {
1204 // no-op
1205 }
1206 });
1207 }
1208 });
1209
1210 // "Stretch" is not selected by default.
1211 setStretchWallpaperPositionButtonSelected(stretchOptionBtn, false /* isSelected */);
1212 stretchOptionBtn.setOnClickListener(new OnClickListener() {
1213 @Override
1214 public void onClick(View view) {
1215 mWallpaperPersister.setIndividualWallpaperWithPosition(TopLevelPickerActivity.this,
1216 wallpaperInfo, WallpaperPersister.WALLPAPER_POSITION_STRETCH,
1217 new SetWallpaperCallback() {
1218 @Override
1219 public void onSuccess() {
1220 // The user may have closed the activity before the set wallpaper operation
1221 // completed.
1222 if (isDestroyed()) {
1223 return;
1224 }
1225
1226 refreshCurrentWallpapers(null /* refreshListener */);
1227
1228 setStretchWallpaperPositionButtonSelected(stretchOptionBtn, true /* isSelected */);
1229 setCenterCropWallpaperPositionButtonSelected(
1230 centerCropOptionBtn, false /* isSelected */);
1231 setCenterWallpaperPositionButtonSelected(centerOptionBtn, false /* isSelected */);
1232
1233 mCustomPhotoWallpaperPosition = WallpaperPersister.WALLPAPER_POSITION_STRETCH;
1234 }
1235
1236 @Override
1237 public void onError(@Nullable Throwable throwable) {
1238 // no-op
1239 }
1240 });
1241 }
1242 });
1243
1244 // "Center" is not selected by default.
1245 setCenterWallpaperPositionButtonSelected(centerOptionBtn, false /* isSelected */);
1246 centerOptionBtn.setOnClickListener(new OnClickListener() {
1247 @Override
1248 public void onClick(View view) {
1249 mWallpaperPersister.setIndividualWallpaperWithPosition(TopLevelPickerActivity.this,
1250 wallpaperInfo, WallpaperPersister.WALLPAPER_POSITION_CENTER,
1251 new SetWallpaperCallback() {
1252 @Override
1253 public void onSuccess() {
1254 // The user may have closed the activity before the set wallpaper operation
1255 // completed.
1256 if (isDestroyed()) {
1257 return;
1258 }
1259
1260 refreshCurrentWallpapers(null /* refreshListener */);
1261
1262 setCenterWallpaperPositionButtonSelected(centerOptionBtn, true /* isSelected */);
1263 setCenterCropWallpaperPositionButtonSelected(
1264 centerCropOptionBtn, false /* isSelected */);
1265 setStretchWallpaperPositionButtonSelected(stretchOptionBtn, false /* isSelected */);
1266
1267 mCustomPhotoWallpaperPosition = WallpaperPersister.WALLPAPER_POSITION_CENTER;
1268 }
1269
1270 @Override
1271 public void onError(@Nullable Throwable throwable) {
1272 // no-op
1273 }
1274 });
1275 }
1276 });
1277 }
1278
1279 private void setCenterWallpaperPositionButtonSelected(Button button, boolean isSelected) {
1280 int drawableId = isSelected ? R.drawable.center_blue : R.drawable.center_grey;
1281 ButtonDrawableSetterCompat.setDrawableToButtonStart(button, getDrawable(drawableId));
1282 button.setTextColor(getColor(getTextColorIdForWallpaperPositionButton(isSelected)));
1283 }
1284
1285 private void setCenterCropWallpaperPositionButtonSelected(Button button, boolean isSelected) {
1286 int drawableId = isSelected ? R.drawable.center_crop_blue : R.drawable.center_crop_grey;
1287 ButtonDrawableSetterCompat.setDrawableToButtonStart(button, getDrawable(drawableId));
1288 button.setTextColor(getColor(getTextColorIdForWallpaperPositionButton(isSelected)));
1289 }
1290
1291 private void setStretchWallpaperPositionButtonSelected(Button button, boolean isSelected) {
1292 int drawableId = isSelected ? R.drawable.stretch_blue : R.drawable.stretch_grey;
1293 ButtonDrawableSetterCompat.setDrawableToButtonStart(button, getDrawable(drawableId));
1294 button.setTextColor(getColor(getTextColorIdForWallpaperPositionButton(isSelected)));
1295 }
1296
1297 private void showSettingWallpaperProgressDialog() {
1298 // ProgressDialog endlessly updates the UI thread, keeping it from going idle which therefore
1299 // causes Espresso to hang once the dialog is shown.
1300 if (!mTestingMode) {
1301 int themeResId;
1302 if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
1303 themeResId = R.style.ProgressDialogThemePreL;
1304 } else {
1305 themeResId = R.style.LightDialogTheme;
1306 }
1307 mSetWallpaperProgressDialog = new ProgressDialog(this, themeResId);
1308 mSetWallpaperProgressDialog.setTitle(null);
1309 mSetWallpaperProgressDialog.setMessage(
1310 getResources().getString(R.string.set_wallpaper_progress_message));
1311 mSetWallpaperProgressDialog.setIndeterminate(true);
1312 mSetWallpaperProgressDialog.show();
1313 }
1314 }
1315
1316 private void dismissSettingWallpaperProgressDialog() {
1317 if (mSetWallpaperProgressDialog != null) {
1318 mSetWallpaperProgressDialog.dismiss();
1319 }
1320 }
1321
1322 private void showSetWallpaperErrorDialog() {
1323 SetWallpaperErrorDialogFragment dialogFragment = SetWallpaperErrorDialogFragment.newInstance(
1324 R.string.set_wallpaper_error_message, WallpaperPersister.DEST_BOTH);
1325
1326 if (isSafeToCommitFragmentTransaction()) {
1327 dialogFragment.show(getSupportFragmentManager(), TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT);
1328 } else {
1329 mStagedSetWallpaperErrorDialogFragment = dialogFragment;
1330 }
1331 }
1332
1333 private interface AssetReceiver {
1334 void onAssetReceived(Asset asset);
1335 }
1336
1337 /**
1338 * An AsyncTask for asynchronously fetching the thumbnail asset for a given WallpaperInfo.
1339 * Used to work around expensive method call to WallpaperManager#getWallpaperFile made from the
1340 * CurrentWallpaperInfoVN getAsset() method.
1341 */
1342 private static class FetchThumbAssetTask extends AsyncTask<Void, Void, Asset> {
1343 private Context mAppContext;
1344 private WallpaperInfo mWallpaperInfo;
1345 private AssetReceiver mReceiver;
1346
1347 public FetchThumbAssetTask(Context appContext, WallpaperInfo wallpaperInfo,
1348 AssetReceiver receiver) {
1349 mAppContext = appContext;
1350 mWallpaperInfo = wallpaperInfo;
1351 mReceiver = receiver;
1352 }
1353
1354 @Override
1355 protected Asset doInBackground(Void... params) {
1356 return mWallpaperInfo.getThumbAsset(mAppContext);
1357 }
1358
1359 @Override
1360 protected void onPostExecute(Asset thumbAsset) {
1361 mReceiver.onAssetReceived(thumbAsset);
1362 }
1363 }
1364}